/*!
    \file generator_kotlin.cpp
    \brief Fast binary encoding Kotlin generator implementation
    \author Ivan Shynkarenka
    \date 03.10.2018
    \copyright MIT License
*/

#include "generator_kotlin.h"

namespace FBE {

void GeneratorKotlin::Generate(const std::shared_ptr<Package>& package)
{
    std::string domain = (package->domain && !package->domain->empty()) ? (*package->domain + ".") : "";

    GenerateFBEPackage(domain, "fbe");
    GenerateFBEUUIDGenerator(domain, "fbe");
    GenerateFBEBuffer(domain, "fbe");
    GenerateFBEModel(domain, "fbe");
    GenerateFBEFieldModel(domain, "fbe");
    GenerateFBEFieldModel(domain, "fbe", "Boolean", "Boolean", "", "1", "false");
    GenerateFBEFieldModel(domain, "fbe", "Byte", "Byte", "", "1", "0.toByte()");
    GenerateFBEFieldModel(domain, "fbe", "Char", "Char", ".toByte()", "1", "'\\u0000'");
    GenerateFBEFieldModel(domain, "fbe", "WChar", "Char", ".toInt()", "4", "'\\u0000'");
    GenerateFBEFieldModel(domain, "fbe", "Int8", "Byte", "", "1", "0.toByte()");
    GenerateFBEFieldModel(domain, "fbe", "UInt8", "UByte", "", "1", "0.toUByte()");
    GenerateFBEFieldModel(domain, "fbe", "Int16", "Short", "", "2", "0.toShort()");
    GenerateFBEFieldModel(domain, "fbe", "UInt16", "UShort", "", "2", "0.toUShort()");
    GenerateFBEFieldModel(domain, "fbe", "Int32", "Int", "", "4", "0");
    GenerateFBEFieldModel(domain, "fbe", "UInt32", "UInt", "", "4", "0u");
    GenerateFBEFieldModel(domain, "fbe", "Int64", "Long", "", "8", "0L");
    GenerateFBEFieldModel(domain, "fbe", "UInt64", "ULong", "", "8", "0uL");
    GenerateFBEFieldModel(domain, "fbe", "Float", "Float", "", "4", "0.0f");
    GenerateFBEFieldModel(domain, "fbe", "Double", "Double", "", "8", "0.0");
    GenerateFBEFieldModel(domain, "fbe", "UUID", "java.util.UUID", "", "16", "UUIDGenerator.nil()");
    GenerateFBEFieldModelDecimal(domain, "fbe");
    if (Version() < 8)
        GenerateFBEFieldModelDate(domain, "fbe");
    else
        GenerateFBEFieldModelTimestamp(domain, "fbe");
    GenerateFBEFieldModelBytes(domain, "fbe");
    GenerateFBEFieldModelString(domain, "fbe");
    if (Final())
    {
        GenerateFBESize(domain, "fbe");
        GenerateFBEFinalModel(domain, "fbe");
        GenerateFBEFinalModel(domain, "fbe", "Boolean", "Boolean", "", "1", "false");
        GenerateFBEFinalModel(domain, "fbe", "Byte", "Byte", "", "1", "0.toByte()");
        GenerateFBEFinalModel(domain, "fbe", "Char", "Char", ".toByte()", "1", "'\\u0000'");
        GenerateFBEFinalModel(domain, "fbe", "WChar", "Char", ".toInt()", "4", "'\\u0000'");
        GenerateFBEFinalModel(domain, "fbe", "Int8", "Byte", "", "1", "0.toByte()");
        GenerateFBEFinalModel(domain, "fbe", "UInt8", "UByte", "", "1", "0.toUByte()");
        GenerateFBEFinalModel(domain, "fbe", "Int16", "Short", "", "2", "0.toShort()");
        GenerateFBEFinalModel(domain, "fbe", "UInt16", "UShort", "", "2", "0.toUShort()");
        GenerateFBEFinalModel(domain, "fbe", "Int32", "Int", "", "4", "0");
        GenerateFBEFinalModel(domain, "fbe", "UInt32", "UInt", "", "4", "0u");
        GenerateFBEFinalModel(domain, "fbe", "Int64", "Long", "", "8", "0L");
        GenerateFBEFinalModel(domain, "fbe", "UInt64", "ULong", "", "8", "0uL");
        GenerateFBEFinalModel(domain, "fbe", "Float", "Float", "", "4", "0.0f");
        GenerateFBEFinalModel(domain, "fbe", "Double", "Double", "", "8", "0.0");
        GenerateFBEFinalModel(domain, "fbe", "UUID", "java.util.UUID", "", "16", "UUIDGenerator.nil()");
        GenerateFBEFinalModelDecimal(domain, "fbe");
        if (Version() < 8)
            GenerateFBEFinalModelDate(domain, "fbe");
        else
            GenerateFBEFinalModelTimestamp(domain, "fbe");
        GenerateFBEFinalModelBytes(domain, "fbe");
        GenerateFBEFinalModelString(domain, "fbe");
    }
    if (Proto())
    {
        GenerateFBESender(domain, "fbe");
        GenerateFBESenderListener(domain, "fbe");
        GenerateFBEReceiver(domain, "fbe");
        GenerateFBEReceiverListener(domain, "fbe");
        GenerateFBEClient(domain, "fbe");
        GenerateFBEClientListener(domain, "fbe");
    }
    if (JSON())
        GenerateFBEJson(domain, "fbe");

    GeneratePackage(package);
}

void GeneratorKotlin::GenerateHeader(const std::string& source)
{
    std::string code = R"CODE(// Automatically generated by the Fast Binary Encoding compiler, do not modify!
// https://github.com/chronoxor/FastBinaryEncoding
// Source: _INPUT_
// Version: _VERSION_

@file:Suppress("UnusedImport", "unused")
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_INPUT_"), source);
    code = std::regex_replace(code, std::regex("_VERSION_"), version);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);
}

void GeneratorKotlin::GenerateFooter()
{
}

void GeneratorKotlin::GenerateImports(const std::string& domain, const std::string& package)
{
    // Generate package name
    WriteLine();
    WriteLineIndent("package " + domain + package);
}

void GeneratorKotlin::GenerateImports(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";

    // Generate common import
    GenerateImports(domain, *p->name);
}

void GeneratorKotlin::GenerateFBEPackage(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Create FBE package path
    CppCommon::Directory::CreateTree(path);
}

void GeneratorKotlin::GenerateFBEUUIDGenerator(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "UUIDGenerator.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding UUID generator
object UUIDGenerator
{
    // Gregorian epoch
    private const val GregorianEpoch = 0xFFFFF4E2F964AC00uL

    // Kotlin constants workaround
    private val Sign = java.lang.Long.parseUnsignedLong("8000000000000000", 16)
    private val Low = java.lang.Long.parseUnsignedLong("00000000FFFFFFFF", 16)
    private val Mid = java.lang.Long.parseUnsignedLong("0000FFFF00000000", 16)
    private val High = java.lang.Long.parseUnsignedLong("FFFF000000000000", 16)

    // Lock and random generator
    private val lock = Object()
    private val generator = java.util.Random()

    // Node & clock sequence bytes
    private val node = makeNode()
    private var nodeAndClockSequence = makeNodeAndClockSequence()

    // Last UUID generated timestamp
    private var last = GregorianEpoch

    private fun makeNode(): ULong = generator.nextLong().toULong() or 0x0000010000000000uL

    private fun makeNodeAndClockSequence(): ULong
    {
        val clock = generator.nextLong().toULong()

        var lsb: ULong = 0u
        // Variant (2 bits)
        lsb = lsb or 0x8000000000000000uL
        // Clock sequence (14 bits)
        lsb = lsb or ((clock and 0x0000000000003FFFuL) shl 48)
        // 6 bytes
        lsb = lsb or node
        return lsb
    }

    // Generate nil UUID0 (all bits set to zero)
    fun nil(): java.util.UUID = java.util.UUID(0, 0)

    // Generate sequential UUID1 (time based version)
    fun sequential(): java.util.UUID
    {
        val now = System.currentTimeMillis().toULong()

        // Generate new clock sequence bytes to get rid of UUID duplicates
        synchronized(lock)
        {
            if (now <= last)
                nodeAndClockSequence = makeNodeAndClockSequence()
            last = now
        }

        val nanosSince = (now - GregorianEpoch) * 10000u

        var msb = 0uL
        msb = msb or (0x00000000FFFFFFFFuL and nanosSince).shl(32)
        msb = msb or (0x0000FFFF00000000uL and nanosSince).shr(16)
        msb = msb or (0xFFFF000000000000uL and nanosSince).shr(48)
        // Sets the version to 1
        msb = msb or 0x0000000000001000uL

        return java.util.UUID(msb.toLong(), nodeAndClockSequence.toLong())
    }

    // Generate random UUID4 (randomly or pseudo-randomly generated version)
    fun random(): java.util.UUID = java.util.UUID.randomUUID()
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEBuffer(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Buffer.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding buffer based on dynamic byte array
class Buffer
{
    // Is the buffer empty?
    val empty: Boolean
        get() = size == 0L
    // Get bytes memory buffer
    var data = ByteArray(0)
        private set
    // Get bytes memory buffer capacity
    val capacity: Long
        get() = data.size.toLong()
    // Get bytes memory buffer size
    var size: Long = 0
        private set
    // Get bytes memory buffer offset
    var offset: Long = 0
        private set

    // Initialize a new expandable buffer with zero capacity
    constructor()
    // Initialize a new expandable buffer with the given capacity
    constructor(capacity: Long) { attach(capacity) }
    // Initialize a new buffer based on the specified byte array
    constructor(buffer: ByteArray) { attach(buffer) }
    // Initialize a new buffer based on the specified region (offset) of a byte array
    constructor(buffer: ByteArray, offset: Long) { attach(buffer, offset) }
    // Initialize a new buffer based on the specified region (size and offset) of a byte array
    constructor(buffer: ByteArray, size: Long, offset: Long) { attach(buffer, size, offset) }

    // Attach memory buffer methods
    fun attach() { data = ByteArray(0); size = 0; offset = 0 }
    fun attach(capacity: Long) { data = ByteArray(capacity.toInt()); size = 0; offset = 0 }
    fun attach(buffer: ByteArray) { data = buffer; size = buffer.size.toLong(); offset = 0 }
    fun attach(buffer: ByteArray, offset: Long) { data = buffer; size = buffer.size.toLong(); this.offset = offset }
    fun attach(buffer: ByteArray, size: Long, offset: Long) { data = buffer; this.size = size; this.offset = offset }

    // Allocate memory in the current buffer and return offset to the allocated memory block
    fun allocate(size: Long): Long
    {
        assert(size >= 0) { "Invalid allocation size!" }
        if (size < 0)
            throw IllegalArgumentException("Invalid allocation size!")

        val offset = this.size

        // Calculate a new buffer size
        val total = this.size + size

        if (total <= data.size)
        {
            this.size = total
            return offset
        }

        val data = ByteArray(kotlin.math.max(total, 2L * this.data.size).toInt())
        System.arraycopy(this.data, 0, data, 0, this.size.toInt())
        this.data = data
        this.size = total
        return offset
    }

    // Remove some memory of the given size from the current buffer
    fun remove(offset: Long, size: Long)
    {
        assert((offset + size) <= this.size) { "Invalid offset & size!" }
        if ((offset + size) > this.size)
            throw IllegalArgumentException("Invalid offset & size!")

        System.arraycopy(data, (offset + size).toInt(), data, offset.toInt(), (this.size - size - offset).toInt())
        this.size -= size
        if (this.offset >= offset + size)
            this.offset -= size
        else if (this.offset >= offset)
        {
            this.offset -= this.offset - offset
            if (this.offset > this.size)
                this.offset = this.size
        }
    }

    // Reserve memory of the given capacity in the current buffer
    fun reserve(capacity: Long)
    {
        assert(capacity >= 0) { "Invalid reserve capacity!" }
        if (capacity < 0)
            throw IllegalArgumentException("Invalid reserve capacity!")

        if (capacity > data.size)
        {
            val data = ByteArray(kotlin.math.max(capacity, 2L * this.data.size).toInt())
            System.arraycopy(this.data, 0, data, 0, size.toInt())
            this.data = data
        }
    }

    // Resize the current buffer
    fun resize(size: Long)
    {
        reserve(size)
        this.size = size
        if (offset > this.size)
            offset = this.size
    }

    // Reset the current buffer and its offset
    fun reset()
    {
        size = 0
        offset = 0
    }

    // Shift the current buffer offset
    fun shift(offset: Long) { this.offset += offset }
    // Unshift the current buffer offset
    fun unshift(offset: Long) { this.offset -= offset }

    companion object
    {
        // Buffer I/O methods

        fun readBoolean(buffer: ByteArray, offset: Long): Boolean
        {
            val index = offset.toInt()
            return buffer[index].toInt() != 0
        }

        fun readByte(buffer: ByteArray, offset: Long): Byte
        {
            val index = offset.toInt()
            return buffer[index]
        }

        fun readChar(buffer: ByteArray, offset: Long): Char
        {
            return readInt8(buffer, offset).toChar()
        }

        fun readWChar(buffer: ByteArray, offset: Long): Char
        {
            return readInt32(buffer, offset).toChar()
        }

        fun readInt8(buffer: ByteArray, offset: Long): Byte
        {
            val index = offset.toInt()
            return buffer[index]
        }

        fun readUInt8(buffer: ByteArray, offset: Long): UByte
        {
            val index = offset.toInt()
            return buffer[index].toUByte()
        }

        fun readInt16(buffer: ByteArray, offset: Long): Short
        {
            val index = offset.toInt()
            return (((buffer[index + 0].toInt() and 0xFF) shl 0) or
                    ((buffer[index + 1].toInt() and 0xFF) shl 8)).toShort()
        }

        fun readUInt16(buffer: ByteArray, offset: Long): UShort
        {
            val index = offset.toInt()
            return (((buffer[index + 0].toUInt() and 0xFFu) shl 0) or
                    ((buffer[index + 1].toUInt() and 0xFFu) shl 8)).toUShort()
        }

        fun readInt32(buffer: ByteArray, offset: Long): Int
        {
            val index = offset.toInt()
            return ((buffer[index + 0].toInt() and 0xFF) shl  0) or
                   ((buffer[index + 1].toInt() and 0xFF) shl  8) or
                   ((buffer[index + 2].toInt() and 0xFF) shl 16) or
                   ((buffer[index + 3].toInt() and 0xFF) shl 24)
        }

        fun readUInt32(buffer: ByteArray, offset: Long): UInt
        {
            val index = offset.toInt()
            return ((buffer[index + 0].toUInt() and 0xFFu) shl  0) or
                   ((buffer[index + 1].toUInt() and 0xFFu) shl  8) or
                   ((buffer[index + 2].toUInt() and 0xFFu) shl 16) or
                   ((buffer[index + 3].toUInt() and 0xFFu) shl 24)
        }

        fun readInt64(buffer: ByteArray, offset: Long): Long
        {
            val index = offset.toInt()
            return ((buffer[index + 0].toLong() and 0xFF) shl  0) or
                   ((buffer[index + 1].toLong() and 0xFF) shl  8) or
                   ((buffer[index + 2].toLong() and 0xFF) shl 16) or
                   ((buffer[index + 3].toLong() and 0xFF) shl 24) or
                   ((buffer[index + 4].toLong() and 0xFF) shl 32) or
                   ((buffer[index + 5].toLong() and 0xFF) shl 40) or
                   ((buffer[index + 6].toLong() and 0xFF) shl 48) or
                   ((buffer[index + 7].toLong() and 0xFF) shl 56)
        }

        fun readUInt64(buffer: ByteArray, offset: Long): ULong
        {
            val index = offset.toInt()
            return ((buffer[index + 0].toULong() and 0xFFu) shl  0) or
                   ((buffer[index + 1].toULong() and 0xFFu) shl  8) or
                   ((buffer[index + 2].toULong() and 0xFFu) shl 16) or
                   ((buffer[index + 3].toULong() and 0xFFu) shl 24) or
                   ((buffer[index + 4].toULong() and 0xFFu) shl 32) or
                   ((buffer[index + 5].toULong() and 0xFFu) shl 40) or
                   ((buffer[index + 6].toULong() and 0xFFu) shl 48) or
                   ((buffer[index + 7].toULong() and 0xFFu) shl 56)
        }

        private fun readInt64BE(buffer: ByteArray, offset: Long): Long
        {
            val index = offset.toInt()
            return ((buffer[index + 0].toLong() and 0xFF) shl 56) or
                   ((buffer[index + 1].toLong() and 0xFF) shl 48) or
                   ((buffer[index + 2].toLong() and 0xFF) shl 40) or
                   ((buffer[index + 3].toLong() and 0xFF) shl 32) or
                   ((buffer[index + 4].toLong() and 0xFF) shl 24) or
                   ((buffer[index + 5].toLong() and 0xFF) shl 16) or
                   ((buffer[index + 6].toLong() and 0xFF) shl  8) or
                   ((buffer[index + 7].toLong() and 0xFF) shl  0)
        }

        fun readFloat(buffer: ByteArray, offset: Long): Float
        {
            val bits = readInt32(buffer, offset)
            return java.lang.Float.intBitsToFloat(bits)
        }

        fun readDouble(buffer: ByteArray, offset: Long): Double
        {
            val bits = readInt64(buffer, offset)
            return java.lang.Double.longBitsToDouble(bits)
        }

        fun readBytes(buffer: ByteArray, offset: Long, size: Long): ByteArray
        {
            val result = ByteArray(size.toInt())
            System.arraycopy(buffer, offset.toInt(), result, 0, size.toInt())
            return result
        }

        fun readString(buffer: ByteArray, offset: Long, size: Long): String
        {
            return String(buffer, offset.toInt(), size.toInt(), java.nio.charset.StandardCharsets.UTF_8)
        }

        fun readUUID(buffer: ByteArray, offset: Long): java.util.UUID
        {
            return java.util.UUID(readInt64BE(buffer, offset), readInt64BE(buffer, offset + 8))
        }

        fun write(buffer: ByteArray, offset: Long, value: Boolean)
        {
            buffer[offset.toInt()] = (if (value) 1 else 0).toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: Byte)
        {
            buffer[offset.toInt()] = value
        }

        fun write(buffer: ByteArray, offset: Long, value: UByte)
        {
            buffer[offset.toInt()] = value.toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: Short)
        {
            buffer[offset.toInt() + 0] = (value.toInt() shr 0).toByte()
            buffer[offset.toInt() + 1] = (value.toInt() shr 8).toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: UShort)
        {
            buffer[offset.toInt() + 0] = (value.toUInt() shr 0).toByte()
            buffer[offset.toInt() + 1] = (value.toUInt() shr 8).toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: Int)
        {
            buffer[offset.toInt() + 0] = (value shr  0).toByte()
            buffer[offset.toInt() + 1] = (value shr  8).toByte()
            buffer[offset.toInt() + 2] = (value shr 16).toByte()
            buffer[offset.toInt() + 3] = (value shr 24).toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: UInt)
        {
            buffer[offset.toInt() + 0] = (value shr  0).toByte()
            buffer[offset.toInt() + 1] = (value shr  8).toByte()
            buffer[offset.toInt() + 2] = (value shr 16).toByte()
            buffer[offset.toInt() + 3] = (value shr 24).toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: Long)
        {
            buffer[offset.toInt() + 0] = (value shr  0).toByte()
            buffer[offset.toInt() + 1] = (value shr  8).toByte()
            buffer[offset.toInt() + 2] = (value shr 16).toByte()
            buffer[offset.toInt() + 3] = (value shr 24).toByte()
            buffer[offset.toInt() + 4] = (value shr 32).toByte()
            buffer[offset.toInt() + 5] = (value shr 40).toByte()
            buffer[offset.toInt() + 6] = (value shr 48).toByte()
            buffer[offset.toInt() + 7] = (value shr 56).toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: ULong)
        {
            buffer[offset.toInt() + 0] = (value shr  0).toByte()
            buffer[offset.toInt() + 1] = (value shr  8).toByte()
            buffer[offset.toInt() + 2] = (value shr 16).toByte()
            buffer[offset.toInt() + 3] = (value shr 24).toByte()
            buffer[offset.toInt() + 4] = (value shr 32).toByte()
            buffer[offset.toInt() + 5] = (value shr 40).toByte()
            buffer[offset.toInt() + 6] = (value shr 48).toByte()
            buffer[offset.toInt() + 7] = (value shr 56).toByte()
        }

        private fun writeBE(buffer: ByteArray, offset: Long, value: Long)
        {
            buffer[offset.toInt() + 0] = (value shr 56).toByte()
            buffer[offset.toInt() + 1] = (value shr 48).toByte()
            buffer[offset.toInt() + 2] = (value shr 40).toByte()
            buffer[offset.toInt() + 3] = (value shr 32).toByte()
            buffer[offset.toInt() + 4] = (value shr 24).toByte()
            buffer[offset.toInt() + 5] = (value shr 16).toByte()
            buffer[offset.toInt() + 6] = (value shr  8).toByte()
            buffer[offset.toInt() + 7] = (value shr  0).toByte()
        }

        fun write(buffer: ByteArray, offset: Long, value: Float)
        {
            val bits = java.lang.Float.floatToIntBits(value)
            write(buffer, offset, bits)
        }

        fun write(buffer: ByteArray, offset: Long, value: Double)
        {
            val bits = java.lang.Double.doubleToLongBits(value)
            write(buffer, offset, bits)
        }

        fun write(buffer: ByteArray, offset: Long, value: ByteArray)
        {
            System.arraycopy(value, 0, buffer, offset.toInt(), value.size)
        }

        fun write(buffer: ByteArray, offset: Long, value: ByteArray, valueOffset: Long, valueSize: Long)
        {
            System.arraycopy(value, valueOffset.toInt(), buffer, offset.toInt(), valueSize.toInt())
        }

        fun write(buffer: ByteArray, offset: Long, value: Byte, valueCount: Long)
        {
            for (i in 0 until valueCount)
                buffer[(offset + i).toInt()] = value
        }

        fun write(buffer: ByteArray, offset: Long, value: java.util.UUID)
        {
            writeBE(buffer, offset, value.mostSignificantBits)
            writeBE(buffer, offset + 8, value.leastSignificantBits)
        }
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEModel(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Model.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base model
open class Model
{
    // Get bytes buffer
    var buffer = Buffer()
        private set

    // Initialize a new model
    protected constructor()
    protected constructor(buffer: Buffer) { this.buffer = buffer }

    // Attach memory buffer methods
    fun attach() { buffer.attach() }
    fun attach(capacity: Long) { buffer.attach(capacity) }
    fun attach(buffer: ByteArray) { this.buffer.attach(buffer) }
    fun attach(buffer: ByteArray, offset: Long) { this.buffer.attach(buffer, offset) }
    fun attach(buffer: ByteArray, size: Long, offset: Long) { this.buffer.attach(buffer, size, offset) }
    fun attach(buffer: Buffer) { this.buffer.attach(buffer.data, buffer.size, buffer.offset) }
    fun attach(buffer: Buffer, offset: Long) { this.buffer.attach(buffer.data, buffer.size, offset) }

    // Model buffer operations
    fun allocate(size: Long): Long { return buffer.allocate(size) }
    fun remove(offset: Long, size: Long) { buffer.remove(offset, size) }
    fun reserve(capacity: Long) { buffer.reserve(capacity) }
    fun resize(size: Long) { buffer.resize(size) }
    fun reset() { buffer.reset() }
    fun shift(offset: Long) { buffer.shift(offset) }
    fun unshift(offset: Long) { buffer.unshift(offset) }

    // Buffer I/O methods
    protected fun readUInt32(offset: Long): UInt { return Buffer.readUInt32(buffer.data, buffer.offset + offset) }
    protected fun write(offset: Long, value: UInt) { Buffer.write(buffer.data, buffer.offset + offset, value) }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModel(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModel.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base field model
@Suppress("MemberVisibilityCanBePrivate")
abstract class FieldModel protected constructor(protected var _buffer: Buffer, protected var _offset: Long)
{
    // Field offset
    var fbeOffset: Long
        get() = _offset
        set(value) { _offset = value }
    // Field size
    open val fbeSize: Long = 0
    // Field extra size
    open val fbeExtra: Long = 0

    // Shift the current field offset
    fun fbeShift(size: Long) { _offset += size }
    // Unshift the current field offset
    fun fbeUnshift(size: Long) { _offset -= size }

    // Check if the value is valid
    open fun verify(): Boolean = true

    // Buffer I/O methods
    protected fun readBoolean(offset: Long): Boolean { return Buffer.readBoolean(_buffer.data, _buffer.offset + offset) }
    protected fun readByte(offset: Long): Byte { return Buffer.readByte(_buffer.data, _buffer.offset + offset) }
    protected fun readChar(offset: Long): Char { return Buffer.readChar(_buffer.data, _buffer.offset + offset) }
    protected fun readWChar(offset: Long): Char { return Buffer.readWChar(_buffer.data, _buffer.offset + offset) }
    protected fun readInt8(offset: Long): Byte { return Buffer.readInt8(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt8(offset: Long): UByte { return Buffer.readUInt8(_buffer.data, _buffer.offset + offset) }
    protected fun readInt16(offset: Long): Short { return Buffer.readInt16(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt16(offset: Long): UShort { return Buffer.readUInt16(_buffer.data, _buffer.offset + offset) }
    protected fun readInt32(offset: Long): Int { return Buffer.readInt32(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt32(offset: Long): UInt { return Buffer.readUInt32(_buffer.data, _buffer.offset + offset) }
    protected fun readInt64(offset: Long): Long { return Buffer.readInt64(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt64(offset: Long): ULong { return Buffer.readUInt64(_buffer.data, _buffer.offset + offset) }
    protected fun readFloat(offset: Long): Float { return Buffer.readFloat(_buffer.data, _buffer.offset + offset) }
    protected fun readDouble(offset: Long): Double { return Buffer.readDouble(_buffer.data, _buffer.offset + offset) }
    protected fun readBytes(offset: Long, size: Long): ByteArray { return Buffer.readBytes(_buffer.data, _buffer.offset + offset, size) }
    protected fun readString(offset: Long, size: Long): String { return Buffer.readString(_buffer.data, _buffer.offset + offset, size) }
    protected fun readUUID(offset: Long): java.util.UUID { return Buffer.readUUID(_buffer.data, _buffer.offset + offset) }
    protected fun write(offset: Long, value: Boolean) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Byte) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: UByte) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Short) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: UShort) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Int) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: UInt) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Long) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: ULong) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Float) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Double) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: ByteArray) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: ByteArray, valueOffset: Long, valueSize: Long) { Buffer.write(_buffer.data, _buffer.offset + offset, value, valueOffset, valueSize) }
    protected fun write(offset: Long, value: Byte, valueCount: Long) { Buffer.write(_buffer.data, _buffer.offset + offset, value, valueCount) }
    protected fun write(offset: Long, value: java.util.UUID) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModel(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / ("FieldModel" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding _TYPE_ field model
class FieldModel_NAME_(buffer: Buffer, offset: Long) : FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = _SIZE_

    // Get the value
    fun get(defaults: _TYPE_ = _DEFAULTS_): _TYPE_
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return defaults

        return read_NAME_(fbeOffset)
    }

    // Set the value
    fun set(value: _TYPE_)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        write(fbeOffset, value_BASE_)
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelDecimal(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelDecimal.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding decimal field model
class FieldModelDecimal(buffer: Buffer, offset: Long) : FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = 16

    // Get the decimal value
    fun get(defaults: java.math.BigDecimal = java.math.BigDecimal.valueOf(0L)): java.math.BigDecimal
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return defaults

        val magnitude = readBytes(fbeOffset, 12)
        val scale = readByte(fbeOffset + 14).toInt()
        val signum = if (readByte(fbeOffset + 15) < 0) -1 else 1

        // Reverse magnitude
        for (i in 0 until (magnitude.size / 2))
        {
            val temp = magnitude[i]
            magnitude[i] = magnitude[magnitude.size - i - 1]
            magnitude[magnitude.size - i - 1] = temp
        }

        val unscaled = java.math.BigInteger(signum, magnitude)

        return java.math.BigDecimal(unscaled, scale)
    }

    // Set the decimal value
    fun set(value: java.math.BigDecimal)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        // Get unscaled absolute value
        val unscaled = value.abs().unscaledValue()
        val bitLength = unscaled.bitLength()
        if ((bitLength < 0) || (bitLength > 96))
        {
            // Value too big for .NET Decimal (bit length is limited to [0, 96])
            write(fbeOffset, 0.toByte(), fbeSize)
            return
        }

        // Get byte array
        val unscaledBytes = unscaled.toByteArray()

        // Get scale
        val scale = value.scale()
        if ((scale < 0) || (scale > 28))
        {
            // Value scale exceeds .NET Decimal limit of [0, 28]
            write(fbeOffset, 0.toByte(), fbeSize)
            return
        }

        // Write unscaled value to bytes 0-11
        var index = 0
        var i = unscaledBytes.size - 1
        while ((i >= 0) && (index < 12))
        {
            write(fbeOffset + index, unscaledBytes[i])
            i--
            index++
        }

        // Fill remaining bytes with zeros
        while (index < 14)
        {
            write(fbeOffset + index, 0.toByte())
            index++
        }

        // Write scale at byte 14
        write(fbeOffset + 14, scale.toByte())

        // Write signum at byte 15
        write(fbeOffset + 15, (if (value.signum() < 0) -128 else 0).toByte())
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelDate(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelDate.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding date field model
class FieldModelDate(buffer: Buffer, offset: Long) : FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = 8

    // Get the date value
    fun get(defaults: java.util.Date = java.util.Date(0)): java.util.Date
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return defaults

        val nanoseconds = readInt64(fbeOffset)
        return java.util.Date(nanoseconds / 1000000)
    }

    // Set the date value
    fun set(value: java.util.Date)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val nanoseconds = value.time * 1000000
        write(fbeOffset, nanoseconds.toULong())
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelTimestamp(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelTimestamp.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding timestamp field model
class FieldModelTimestamp(buffer: Buffer, offset: Long) : FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = 8

    // Get the timestamp value
    fun get(defaults: java.time.Instant = java.time.Instant.EPOCH): java.time.Instant
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return defaults

        val nanoseconds = readInt64(fbeOffset)
        return java.time.Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000)
    }

    // Set the timestamp value
    fun set(value: java.time.Instant)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val nanoseconds = value.epochSecond * 1000000000 + value.nano
        write(fbeOffset, nanoseconds.toULong())
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelBytes(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelBytes.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding bytes field model
class FieldModelBytes(buffer: Buffer, offset: Long) : FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = 4

    // Field extra size
    override val fbeExtra: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val fbeBytesOffset = readUInt32(fbeOffset).toLong()
        if ((fbeBytesOffset == 0L) || ((_buffer.offset + fbeBytesOffset + 4) > _buffer.size))
            return 0

        val fbeBytesSize = readUInt32(fbeBytesOffset).toLong()
        return 4 + fbeBytesSize
    }

    // Check if the bytes value is valid
    override fun verify(): Boolean
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return true

        val fbeBytesOffset = readUInt32(fbeOffset).toLong()
        if (fbeBytesOffset == 0L)
            return true

        if ((_buffer.offset + fbeBytesOffset + 4) > _buffer.size)
            return false

        val fbeBytesSize = readUInt32(fbeBytesOffset).toLong()
        if ((_buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.size)
            return false

        return true
    }

    // Get the bytes value
    fun get(defaults: ByteArray = ByteArray(0)): ByteArray
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return defaults

        val fbeBytesOffset = readUInt32(fbeOffset).toLong()
        if (fbeBytesOffset == 0L)
            return defaults

        assert((_buffer.offset + fbeBytesOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeBytesOffset + 4) > _buffer.size)
            return defaults

        val fbeBytesSize = readUInt32(fbeBytesOffset).toLong()
        assert((_buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.size)
            return defaults

        return readBytes(fbeBytesOffset + 4, fbeBytesSize)
    }

    // Set the bytes value
    fun set(value: ByteArray)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeBytesSize = value.size.toLong()
        val fbeBytesOffset = _buffer.allocate(4 + fbeBytesSize) - _buffer.offset
        assert((fbeBytesOffset > 0) && ((_buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) <= _buffer.size)) { "Model is broken!" }
        if ((fbeBytesOffset <= 0) || ((_buffer.offset + fbeBytesOffset + 4 + fbeBytesSize) > _buffer.size))
            return

        write(fbeOffset, fbeBytesOffset.toUInt())
        write(fbeBytesOffset, fbeBytesSize.toUInt())
        write(fbeBytesOffset + 4, value)
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelString(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FieldModelString.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding string field model
class FieldModelString(buffer: Buffer, offset: Long) : FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = 4

    // Field extra size
    override val fbeExtra: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val fbeStringOffset = readUInt32(fbeOffset).toLong()
        if ((fbeStringOffset == 0L) || ((_buffer.offset + fbeStringOffset + 4) > _buffer.size))
            return 0

        val fbeStringSize = readUInt32(fbeStringOffset).toLong()
        return 4 + fbeStringSize
    }

    // Check if the string value is valid
    override fun verify(): Boolean
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return true

        val fbeStringOffset = readUInt32(fbeOffset).toLong()
        if (fbeStringOffset == 0L)
            return true

        if ((_buffer.offset + fbeStringOffset + 4) > _buffer.size)
            return false

        val fbeStringSize = readUInt32(fbeStringOffset).toLong()
        if ((_buffer.offset + fbeStringOffset + 4 + fbeStringSize) > _buffer.size)
            return false

        return true
    }

    // Get the string value
    fun get(defaults: String = ""): String
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return defaults

        val fbeStringOffset = readUInt32(fbeOffset).toLong()
        if (fbeStringOffset == 0L)
            return defaults

        assert((_buffer.offset + fbeStringOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeStringOffset + 4) > _buffer.size)
            return defaults

        val fbeStringSize = readUInt32(fbeStringOffset).toLong()
        assert((_buffer.offset + fbeStringOffset + 4 + fbeStringSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeStringOffset + 4 + fbeStringSize) > _buffer.size)
            return defaults

        return readString(fbeStringOffset + 4, fbeStringSize)
    }

    // Set the string value
    fun set(value: String)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val bytes = value.toByteArray(java.nio.charset.StandardCharsets.UTF_8)

        val fbeStringSize = bytes.size.toLong()
        val fbeStringOffset = _buffer.allocate(4 + fbeStringSize) - _buffer.offset
        assert((fbeStringOffset > 0) && ((_buffer.offset + fbeStringOffset + 4 + fbeStringSize) <= _buffer.size)) { "Model is broken!" }
        if ((fbeStringOffset <= 0) || ((_buffer.offset + fbeStringOffset + 4 + fbeStringSize) > _buffer.size))
            return

        write(fbeOffset, fbeStringOffset.toUInt())
        write(fbeStringOffset, fbeStringSize.toUInt())
        write(fbeStringOffset + 4, bytes)
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelOptional(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelOptional" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding optional _NAME_ field model
class FieldModelOptional_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = 1 + 4

    // Field extra size
    override val fbeExtra: Long get()
    {
        if (!hasValue())
            return 0

        val fbeOptionalOffset = readUInt32(fbeOffset + 1).toLong()
        if ((fbeOptionalOffset == 0L) || ((_buffer.offset + fbeOptionalOffset + 4) > _buffer.size))
            return 0

        _buffer.shift(fbeOptionalOffset)
        val fbeResult = value.fbeSize + value.fbeExtra
        _buffer.unshift(fbeOptionalOffset)
        return fbeResult
    }

    // Checks if the object contains a value
    fun hasValue(): Boolean
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return false

        val fbeHasValue = readInt8(fbeOffset).toInt()
        return fbeHasValue != 0
    }

    // Base field model value
    val value = _MODEL_(buffer, 0)

    // Check if the optional value is valid
    override fun verify(): Boolean
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return true

        val fbeHasValue = readInt8(fbeOffset).toInt()
        if (fbeHasValue == 0)
            return true

        val fbeOptionalOffset = readUInt32(fbeOffset + 1).toLong()
        if (fbeOptionalOffset == 0L)
            return false

        _buffer.shift(fbeOptionalOffset)
        val fbeResult = value.verify()
        _buffer.unshift(fbeOptionalOffset)
        return fbeResult
    }

    // Get the optional value (being phase)
    fun getBegin(): Long
    {
        if (!hasValue())
            return 0

        val fbeOptionalOffset = readUInt32(fbeOffset + 1).toLong()
        assert(fbeOptionalOffset > 0) { "Model is broken!" }
        if (fbeOptionalOffset <= 0)
            return 0

        _buffer.shift(fbeOptionalOffset)
        return fbeOptionalOffset
    }

    // Get the optional value (end phase)
    fun getEnd(fbeBegin: Long)
    {
        _buffer.unshift(fbeBegin)
    }

    // Get the optional value
    fun get(defaults: _TYPE_ = null): _TYPE_
    {
        val fbeBegin = getBegin()
        if (fbeBegin == 0L)
            return defaults

        val optional = value.get()
        getEnd(fbeBegin)
        return optional
    }

    // Set the optional value (begin phase)
    fun setBegin(hasValue: Boolean): Long
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val fbeHasValue = if (hasValue) 1 else 0
        write(fbeOffset, fbeHasValue.toByte())
        if (fbeHasValue == 0)
            return 0

        val fbeOptionalSize = value.fbeSize
        val fbeOptionalOffset = _buffer.allocate(fbeOptionalSize) - _buffer.offset
        assert((fbeOptionalOffset > 0) && ((_buffer.offset + fbeOptionalOffset + fbeOptionalSize) <= _buffer.size)) { "Model is broken!" }
        if ((fbeOptionalOffset <= 0) || ((_buffer.offset + fbeOptionalOffset + fbeOptionalSize) > _buffer.size))
            return 0

        write(fbeOffset + 1, fbeOptionalOffset.toUInt())

        _buffer.shift(fbeOptionalOffset)
        return fbeOptionalOffset
    }

    // Set the optional value (end phase)
    fun setEnd(fbeBegin: Long)
    {
        _buffer.unshift(fbeBegin)
    }

    // Set the optional value
    fun set(optional: _TYPE_)
    {
        val fbeBegin = setBegin(optional != null)
        if (fbeBegin == 0L)
            return

        value.set(optional!!)
        setEnd(fbeBegin)
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelArray(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& base, bool optional, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelArray" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ array field model
class FieldModelArray_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long, val size: Long) : _DOMAIN_fbe.FieldModel(buffer, offset)
{
    private val _model = _MODEL_(buffer, offset)

    // Field size
    override val fbeSize: Long = size * _model.fbeSize

    // Field extra size
    override val fbeExtra: Long = 0

    // Get the array offset
    val offset: Long get() = 0

    // Array index operator
    fun getItem(index: Long): _MODEL_
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        assert(index < size) { "Index is out of bounds!" }

        _model.fbeOffset = fbeOffset
        _model.fbeShift(index * _model.fbeSize)
        return _model
    }

    // Check if the array is valid
    override fun verify(): Boolean
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return false

        _model.fbeOffset = fbeOffset
        var i = size
        while (i-- > 0)
        {
            if (!_model.verify())
                return false
            _model.fbeShift(_model.fbeSize)
        }

        return true
    }

    // Get the array
    fun get(): _ARRAY_
    {
        val values = _INIT_

        val fbeModel = getItem(0)
        for (i in 0 until size)
        {
            values[i.toInt()] = fbeModel.get()
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
        return values
    }

    // Get the array
    fun get(values: _ARRAY_)
    {
        val fbeModel = getItem(0)
        var i: Long = 0
        while ((i < values.size) && (i < size))
        {
            values[i.toInt()] = fbeModel.get()
            fbeModel.fbeShift(fbeModel.fbeSize)
            i++
        }
    }

    // Get the array as java.util.ArrayList
    fun get(values: java.util.ArrayList<_TYPE_>)
    {
        values.clear()
        values.ensureCapacity(size.toInt())

        val fbeModel = getItem(0)
        var i = size
        while (i-- > 0)
        {
            val value = fbeModel.get()
            values.add(value)
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
    }

    // Set the array
    fun set(values: _ARRAY_)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeModel = getItem(0)
        var i: Long = 0
        while ((i < values.size) && (i < size))
        {
            fbeModel.set(values[i.toInt()])
            fbeModel.fbeShift(fbeModel.fbeSize)
            i++
        }
    }

    // Set the array as java.util.ArrayList
    fun set(values: java.util.ArrayList<_TYPE_>)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeModel = getItem(0)
        var i: Long = 0
        while ((i < values.size) && (i < size))
        {
            fbeModel.set(values[i.toInt()])
            fbeModel.fbeShift(fbeModel.fbeSize)
            i++
        }
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("_ARRAY_"), "Array<" + type_name + ">");
    if (optional)
        code = std::regex_replace(code, std::regex("_INIT_"), "arrayOfNulls<" + type_name + ">(size.toInt())");
    else
        code = std::regex_replace(code, std::regex("_INIT_"), "Array(size.toInt()) { " + ConvertDefault(domain, package, base) + " }");
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelVector(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelVector" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ vector field model
class FieldModelVector_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FieldModel(buffer, offset)
{
    private val _model = _MODEL_(buffer, offset)

    // Field size
    override val fbeSize: Long = 4

    // Field extra size
    override val fbeExtra: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val fbeVectorOffset = readUInt32(fbeOffset).toLong()
        if ((fbeVectorOffset == 0L) || ((_buffer.offset + fbeVectorOffset + 4) > _buffer.size))
            return 0

        val fbeVectorSize = readUInt32(fbeVectorOffset).toLong()

        var fbeResult: Long = 4
        _model.fbeOffset = fbeVectorOffset + 4
        var i = fbeVectorSize
        while (i-- > 0)
        {
            fbeResult += _model.fbeSize + _model.fbeExtra
            _model.fbeShift(_model.fbeSize)
        }
        return fbeResult
    }

    // Get the vector offset
    val offset: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        return readUInt32(fbeOffset).toLong()
    }

    // Get the vector size
    val size: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val fbeVectorOffset = readUInt32(fbeOffset).toLong()
        if ((fbeVectorOffset == 0L) || ((_buffer.offset + fbeVectorOffset + 4) > _buffer.size))
            return 0

        return readUInt32(fbeVectorOffset).toLong()
    }

    // Vector index operator
    fun getItem(index: Long): _MODEL_
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }

        val fbeVectorOffset = readUInt32(fbeOffset).toLong()
        assert((fbeVectorOffset > 0) && ((_buffer.offset + fbeVectorOffset + 4) <= _buffer.size)) { "Model is broken!" }

        val fbeVectorSize = readUInt32(fbeVectorOffset).toLong()
        assert(index < fbeVectorSize) { "Index is out of bounds!" }

        _model.fbeOffset = fbeVectorOffset + 4
        _model.fbeShift(index * _model.fbeSize)
        return _model
    }

    // Resize the vector and get its first model
    fun resize(size: Long): _MODEL_
    {
        val fbeVectorSize = size * _model.fbeSize
        val fbeVectorOffset = _buffer.allocate(4 + fbeVectorSize) - _buffer.offset
        assert((fbeVectorOffset > 0) && ((_buffer.offset + fbeVectorOffset + 4) <= _buffer.size)) { "Model is broken!" }

        write(fbeOffset, fbeVectorOffset.toUInt())
        write(fbeVectorOffset, size.toUInt())
        write(fbeVectorOffset + 4, 0.toByte(), fbeVectorSize)

        _model.fbeOffset = fbeVectorOffset + 4
        return _model
    }

    // Check if the vector is valid
    override fun verify(): Boolean
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return true

        val fbeVectorOffset = readUInt32(fbeOffset).toLong()
        if (fbeVectorOffset == 0L)
            return true

        if ((_buffer.offset + fbeVectorOffset + 4) > _buffer.size)
            return false

        val fbeVectorSize = readUInt32(fbeVectorOffset).toLong()

        _model.fbeOffset = fbeVectorOffset + 4
        var i = fbeVectorSize
        while (i-- > 0)
        {
            if (!_model.verify())
                return false
            _model.fbeShift(_model.fbeSize)
        }

        return true
    }

    // Get the vector as java.util.ArrayList
    operator fun get(values: java.util.ArrayList<_TYPE_>)
    {
        values.clear()

        val fbeVectorSize = size
        if (fbeVectorSize == 0L)
            return

        values.ensureCapacity(fbeVectorSize.toInt())

        val fbeModel = getItem(0)
        var i = fbeVectorSize
        while (i-- > 0)
        {
            val value = fbeModel.get()
            values.add(value)
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
    }

    // Get the vector as java.util.LinkedList
    operator fun get(values: java.util.LinkedList<_TYPE_>)
    {
        values.clear()

        val fbeVectorSize = size
        if (fbeVectorSize == 0L)
            return

        val fbeModel = getItem(0)
        var i = fbeVectorSize
        while (i-- > 0)
        {
            val value = fbeModel.get()
            values.add(value)
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
    }

    // Get the vector as java.util.HashSet
    operator fun get(values: java.util.HashSet<_TYPE_>)
    {
        values.clear()

        val fbeVectorSize = size
        if (fbeVectorSize == 0L)
            return

        val fbeModel = getItem(0)
        var i = fbeVectorSize
        while (i-- > 0)
        {
            val value = fbeModel.get()
            values.add(value)
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
    }

    // Set the vector as java.util.ArrayList
    fun set(values: java.util.ArrayList<_TYPE_>)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeModel = resize(values.size.toLong())
        for (value in values)
        {
            fbeModel.set(value)
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
    }

    // Set the vector as java.util.LinkedList
    fun set(values: java.util.LinkedList<_TYPE_>)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeModel = resize(values.size.toLong())
        for (value in values)
        {
            fbeModel.set(value)
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
    }

    // Set the vector as java.util.HashSet
    fun set(values: java.util.HashSet<_TYPE_>)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeModel = resize(values.size.toLong())
        for (value in values)
        {
            fbeModel.set(value)
            fbeModel.fbeShift(fbeModel.fbeSize)
        }
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelMap(const std::string& domain, const std::string& package, const std::string& key_name, const std::string& key_type, const std::string& key_model, const std::string& value_name, const std::string& value_type, const std::string& value_model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModelMap" + key_name + value_name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _KEY_NAME_->_VALUE_NAME_ map field model
class FieldModelMap_KEY_NAME__VALUE_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FieldModel(buffer, offset)
{
    private val _modelKey = _KEY_MODEL_(buffer, offset)
    private val _modelValue = _VALUE_MODEL_(buffer, offset)

    // Field size
    override val fbeSize: Long = 4

    // Field extra size
    override val fbeExtra: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val fbeMapOffset = readUInt32(fbeOffset).toLong()
        if ((fbeMapOffset == 0L) || ((_buffer.offset + fbeMapOffset + 4) > _buffer.size))
            return 0

        val fbeMapSize = readUInt32(fbeMapOffset).toLong()

        var fbeResult: Long = 4
        _modelKey.fbeOffset = fbeMapOffset + 4
        _modelValue.fbeOffset = fbeMapOffset + 4 + _modelKey.fbeSize
        var i = fbeMapSize
        while (i-- > 0)
        {
            fbeResult += _modelKey.fbeSize + _modelKey.fbeExtra
            _modelKey.fbeShift(_modelKey.fbeSize + _modelValue.fbeSize)

            fbeResult += _modelValue.fbeSize + _modelValue.fbeExtra
            _modelValue.fbeShift(_modelKey.fbeSize + _modelValue.fbeSize)
        }
        return fbeResult
    }

    // Get the map offset
    val offset: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        return readUInt32(fbeOffset).toLong()
    }

    // Get the map size
    val size: Long get()
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val fbeMapOffset = readUInt32(fbeOffset).toLong()
        if ((fbeMapOffset == 0L) || ((_buffer.offset + fbeMapOffset + 4) > _buffer.size))
            return 0

        return readUInt32(fbeMapOffset).toLong()
    }

    // Map index operator
    fun getItem(index: Long): Pair<_KEY_MODEL_, _VALUE_MODEL_>
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }

        val fbeMapOffset = readUInt32(fbeOffset).toLong()
        assert((fbeMapOffset > 0) && ((_buffer.offset + fbeMapOffset + 4) <= _buffer.size)) { "Model is broken!" }

        val fbeMapSize = readUInt32(fbeMapOffset).toLong()
        assert(index < fbeMapSize) { "Index is out of bounds!" }

        _modelKey.fbeOffset = fbeMapOffset + 4
        _modelValue.fbeOffset = fbeMapOffset + 4 + _modelKey.fbeSize
        _modelKey.fbeShift(index * (_modelKey.fbeSize + _modelValue.fbeSize))
        _modelValue.fbeShift(index * (_modelKey.fbeSize + _modelValue.fbeSize))
        return Pair(_modelKey, _modelValue)
    }

    // Resize the map and get its first model
    fun resize(size: Long): Pair<_KEY_MODEL_, _VALUE_MODEL_>
    {
        val fbeMapSize = size * (_modelKey.fbeSize + _modelValue.fbeSize)
        val fbeMapOffset = _buffer.allocate(4 + fbeMapSize) - _buffer.offset
        assert((fbeMapOffset > 0) && ((_buffer.offset + fbeMapOffset + 4) <= _buffer.size)) { "Model is broken!" }

        write(fbeOffset, fbeMapOffset.toUInt())
        write(fbeMapOffset, size.toUInt())
        write(fbeMapOffset + 4, 0.toByte(), fbeMapSize)

        _modelKey.fbeOffset = fbeMapOffset + 4
        _modelValue.fbeOffset = fbeMapOffset + 4 + _modelKey.fbeSize
        return Pair(_modelKey, _modelValue)
    }

    // Check if the map is valid
    override fun verify(): Boolean
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return true

        val fbeMapOffset = readUInt32(fbeOffset).toLong()
        if (fbeMapOffset == 0L)
            return true

        if ((_buffer.offset + fbeMapOffset + 4) > _buffer.size)
            return false

        val fbeMapSize = readUInt32(fbeMapOffset).toLong()

        _modelKey.fbeOffset = fbeMapOffset + 4
        _modelValue.fbeOffset = fbeMapOffset + 4 + _modelKey.fbeSize
        var i = fbeMapSize
        while (i-- > 0)
        {
            if (!_modelKey.verify())
                return false
            _modelKey.fbeShift(_modelKey.fbeSize + _modelValue.fbeSize)
            if (!_modelValue.verify())
                return false
            _modelValue.fbeShift(_modelKey.fbeSize + _modelValue.fbeSize)
        }

        return true
    }

    // Get the map as java.util.TreeMap
    fun get(values: java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_>)
    {
        values.clear()

        val fbeMapSize = size
        if (fbeMapSize == 0L)
            return

        val fbeModel = getItem(0)
        var i = fbeMapSize
        while (i-- > 0)
        {
            val key = fbeModel.first.get()
            val value = fbeModel.second.get()
            values[key] = value
            fbeModel.first.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
            fbeModel.second.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
        }
    }

    // Get the map as java.util.HashMap
    fun get(values: java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_>)
    {
        values.clear()

        val fbeMapSize = size
        if (fbeMapSize == 0L)
            return

        val fbeModel = getItem(0)
        var i = fbeMapSize
        while (i-- > 0)
        {
            val key = fbeModel.first.get()
            val value = fbeModel.second.get()
            values[key] = value
            fbeModel.first.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
            fbeModel.second.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
        }
    }

    // Set the map as java.util.TreeMap
    fun set(values: java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_>)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeModel = resize(values.size.toLong())
        for ((key, value1) in values)
        {
            fbeModel.first.set(key)
            fbeModel.first.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
            fbeModel.second.set(value1)
            fbeModel.second.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
        }
    }

    // Set the map as java.util.HashMap
    fun set(values: java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_>)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        val fbeModel = resize(values.size.toLong())
        for ((key, value1) in values)
        {
            fbeModel.first.set(key)
            fbeModel.first.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
            fbeModel.second.set(value1)
            fbeModel.second.fbeShift(fbeModel.first.fbeSize + fbeModel.second.fbeSize)
        }
    }
}
)CODE";

    std::string key_type_name = IsPackageType(key_type) ? key_type : (domain + package + "." + key_type);
    std::string value_type_name = IsPackageType(value_type) ? value_type : (domain + package + "." + value_type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_KEY_NAME_"), key_name);
    code = std::regex_replace(code, std::regex("_KEY_TYPE_"), key_type_name);
    code = std::regex_replace(code, std::regex("_KEY_MODEL_"), key_model);
    code = std::regex_replace(code, std::regex("_VALUE_NAME_"), value_name);
    code = std::regex_replace(code, std::regex("_VALUE_TYPE_"), value_type_name);
    code = std::regex_replace(code, std::regex("_VALUE_MODEL_"), value_model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFieldModelEnumFlags(const std::string& domain, const std::string& package, const std::string& name, const std::string& type)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModel" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ field model
class FieldModel_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FieldModel(buffer, offset)
{
    // Field size
    override val fbeSize: Long = _SIZE_

    // Get the value
    fun get(defaults: _DOMAIN__PACKAGE_._NAME_ = _DOMAIN__PACKAGE_._NAME_()): _DOMAIN__PACKAGE_._NAME_
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return defaults

        return _DOMAIN__PACKAGE_._NAME_(_READ_(fbeOffset))
    }

    // Set the value
    fun set(value: _DOMAIN__PACKAGE_._NAME_)
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return

        write(fbeOffset, value.raw)
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), package);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_SIZE_"), ConvertEnumSize(type));
    code = std::regex_replace(code, std::regex("_READ_"), ConvertEnumRead(type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBESize(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Size.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding size
class Size
{
    var value: Long = 0

    // Initialize a new size
    constructor()
    constructor(size: Long) { value = size }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModel(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModel.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base final model
@Suppress("MemberVisibilityCanBePrivate")
abstract class FinalModel protected constructor(protected var _buffer: Buffer, protected var _offset: Long)
{
    // Final offset
    var fbeOffset: Long
        get() = _offset
        set(value) { _offset = value }
    // Final size
    open val fbeSize: Long = 0
    // Final extra size
    open val fbeExtra: Long = 0

    // Shift the current final offset
    fun fbeShift(size: Long) { _offset += size }
    // Unshift the current final offset
    fun fbeUnshift(size: Long) { _offset -= size }

    // Check if the value is valid
    abstract fun verify(): Long

    // Buffer I/O methods
    protected fun readBoolean(offset: Long): Boolean { return Buffer.readBoolean(_buffer.data, _buffer.offset + offset) }
    protected fun readByte(offset: Long): Byte { return Buffer.readByte(_buffer.data, _buffer.offset + offset) }
    protected fun readChar(offset: Long): Char { return Buffer.readChar(_buffer.data, _buffer.offset + offset) }
    protected fun readWChar(offset: Long): Char { return Buffer.readWChar(_buffer.data, _buffer.offset + offset) }
    protected fun readInt8(offset: Long): Byte { return Buffer.readInt8(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt8(offset: Long): UByte { return Buffer.readUInt8(_buffer.data, _buffer.offset + offset) }
    protected fun readInt16(offset: Long): Short { return Buffer.readInt16(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt16(offset: Long): UShort { return Buffer.readUInt16(_buffer.data, _buffer.offset + offset) }
    protected fun readInt32(offset: Long): Int { return Buffer.readInt32(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt32(offset: Long): UInt { return Buffer.readUInt32(_buffer.data, _buffer.offset + offset) }
    protected fun readInt64(offset: Long): Long { return Buffer.readInt64(_buffer.data, _buffer.offset + offset) }
    protected fun readUInt64(offset: Long): ULong { return Buffer.readUInt64(_buffer.data, _buffer.offset + offset) }
    protected fun readFloat(offset: Long): Float { return Buffer.readFloat(_buffer.data, _buffer.offset + offset) }
    protected fun readDouble(offset: Long): Double { return Buffer.readDouble(_buffer.data, _buffer.offset + offset) }
    protected fun readBytes(offset: Long, size: Long): ByteArray { return Buffer.readBytes(_buffer.data, _buffer.offset + offset, size) }
    protected fun readString(offset: Long, size: Long): String { return Buffer.readString(_buffer.data, _buffer.offset + offset, size) }
    protected fun readUUID(offset: Long): java.util.UUID { return Buffer.readUUID(_buffer.data, _buffer.offset + offset) }
    protected fun write(offset: Long, value: Boolean) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Byte) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: UByte) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Short) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: UShort) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Int) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: UInt) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Long) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: ULong) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Float) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: Double) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: ByteArray) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
    protected fun write(offset: Long, value: ByteArray, valueOffset: Long, valueSize: Long) { Buffer.write(_buffer.data, _buffer.offset + offset, value, valueOffset, valueSize) }
    protected fun write(offset: Long, value: Byte, valueCount: Long) { Buffer.write(_buffer.data, _buffer.offset + offset, value, valueCount) }
    protected fun write(offset: Long, value: java.util.UUID) { Buffer.write(_buffer.data, _buffer.offset + offset, value) }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModel(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& base, const std::string& size, const std::string& defaults)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / ("FinalModel" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding _TYPE_ final model
class FinalModel_NAME_(buffer: Buffer, offset: Long) : FinalModel(buffer, offset)
{
    // Get the allocation size
    @Suppress("UNUSED_PARAMETER")
    fun fbeAllocationSize(value: _TYPE_): Long = fbeSize

    // Final size
    override val fbeSize: Long = _SIZE_

    // Check if the value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return Long.MAX_VALUE

        return fbeSize
    }

    // Get the value
    fun get(size: Size): _TYPE_
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return _DEFAULTS_

        size.value = fbeSize
        return read_NAME_(fbeOffset)
    }

    // Set the value
    fun set(value: _TYPE_): Long
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        write(fbeOffset, value_BASE_)
        return fbeSize
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type);
    code = std::regex_replace(code, std::regex("_BASE_"), base);
    code = std::regex_replace(code, std::regex("_SIZE_"), size);
    code = std::regex_replace(code, std::regex("_DEFAULTS_"), defaults);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelDecimal(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelDecimal.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding decimal final model
class FinalModelDecimal(buffer: Buffer, offset: Long) : FinalModel(buffer, offset)
{
    // Get the allocation size
    @Suppress("UNUSED_PARAMETER")
    fun fbeAllocationSize(value: java.math.BigDecimal): Long = fbeSize

    // Final size
    override val fbeSize: Long = 16

    // Check if the decimal value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return Long.MAX_VALUE

        return fbeSize
    }

    // Get the decimal value
    fun get(size: Size): java.math.BigDecimal
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return java.math.BigDecimal.valueOf(0L)

        val magnitude = readBytes(fbeOffset, 12)
        val scale = readByte(fbeOffset + 14).toInt()
        val signum = if (readByte(fbeOffset + 15) < 0) -1 else 1

        // Reverse magnitude
        for (i in 0 until (magnitude.size / 2))
        {
            val temp = magnitude[i]
            magnitude[i] = magnitude[magnitude.size - i - 1]
            magnitude[magnitude.size - i - 1] = temp
        }

        val unscaled = java.math.BigInteger(signum, magnitude)

        size.value = fbeSize
        return java.math.BigDecimal(unscaled, scale)
    }

    // Set the decimal value
    fun set(value: java.math.BigDecimal): Long
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        // Get unscaled absolute value
        val unscaled = value.abs().unscaledValue()
        val bitLength = unscaled.bitLength()
        if ((bitLength < 0) || (bitLength > 96))
        {
            // Value too big for .NET Decimal (bit length is limited to [0, 96])
            write(fbeOffset, 0.toByte(), fbeSize)
            return fbeSize
        }

        // Get byte array
        val unscaledBytes = unscaled.toByteArray()

        // Get scale
        val scale = value.scale()
        if ((scale < 0) || (scale > 28))
        {
            // Value scale exceeds .NET Decimal limit of [0, 28]
            write(fbeOffset, 0.toByte(), fbeSize)
            return fbeSize
        }

        // Write unscaled value to bytes 0-11
        var index = 0
        var i = unscaledBytes.size - 1
        while ((i >= 0) && (index < 12))
        {
            write(fbeOffset + index, unscaledBytes[i])
            i--
            index++
        }

        // Fill remaining bytes with zeros
        while (index < 14)
        {
            write(fbeOffset + index, 0.toByte())
            index++
        }

        // Write scale at byte 14
        write(fbeOffset + 14, scale.toByte())

        // Write signum at byte 15
        write(fbeOffset + 15, (if (value.signum() < 0) -128 else 0).toByte())
        return fbeSize
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelDate(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelDate.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding date final model
class FinalModelDate(buffer: Buffer, offset: Long) : FinalModel(buffer, offset)
{
    // Get the allocation size
    @Suppress("UNUSED_PARAMETER")
    fun fbeAllocationSize(value: java.util.Date): Long = fbeSize

    // Final size
    override val fbeSize: Long = 8

    // Check if the date value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return Long.MAX_VALUE

        return fbeSize
    }

    // Get the date value
    fun get(size: Size): java.util.Date
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return java.util.Date(0)

        size.value = fbeSize
        val nanoseconds = readInt64(fbeOffset)
        return java.util.Date(nanoseconds / 1000000)
    }

    // Set the date value
    fun set(value: java.util.Date): Long
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val nanoseconds = value.time * 1000000
        write(fbeOffset, nanoseconds.toULong())
        return fbeSize
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelTimestamp(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelTimestamp.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding timestamp final model
class FinalModelTimestamp(buffer: Buffer, offset: Long) : FinalModel(buffer, offset)
{
    // Get the allocation size
    @Suppress("UNUSED_PARAMETER")
    fun fbeAllocationSize(value: java.time.Instant): Long = fbeSize

    // Final size
    override val fbeSize: Long = 8

    // Check if the timestamp value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return Long.MAX_VALUE

        return fbeSize
    }

    // Get the timestamp value
    fun get(size: Size): java.time.Instant
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return java.time.Instant.EPOCH

        size.value = fbeSize
        val nanoseconds = readInt64(fbeOffset)
        return java.time.Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000)
    }

    // Set the timestamp value
    fun set(value: java.time.Instant): Long
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        val nanoseconds = value.epochSecond * 1000000000 + value.nano
        write(fbeOffset, nanoseconds.toULong())
        return fbeSize
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelBytes(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelBytes.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding bytes final model
class FinalModelBytes(buffer: Buffer, offset: Long) : FinalModel(buffer, offset)
{
    // Get the allocation size
    fun fbeAllocationSize(value: ByteArray): Long = 4 + value.size.toLong()

    // Check if the bytes value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset) + 4 > _buffer.size)
            return Long.MAX_VALUE

        val fbeBytesSize = readUInt32(fbeOffset).toLong()
        if ((_buffer.offset + fbeOffset + 4 + fbeBytesSize) > _buffer.size)
            return Long.MAX_VALUE

        return 4 + fbeBytesSize
    }

    // Get the bytes value
    fun get(size: Size): ByteArray
    {
        if ((_buffer.offset + fbeOffset) + 4 > _buffer.size)
        {
            size.value = 0
            return ByteArray(0)
        }

        val fbeBytesSize = readUInt32(fbeOffset).toLong()
        assert((_buffer.offset + fbeOffset + 4 + fbeBytesSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4 + fbeBytesSize) > _buffer.size)
        {
            size.value = 4
            return ByteArray(0)
        }

        size.value = 4 + fbeBytesSize
        return readBytes(fbeOffset + 4, fbeBytesSize)
    }

    // Set the bytes value
    fun set(value: ByteArray): Long
    {
        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        val fbeBytesSize = value.size.toLong()
        assert((_buffer.offset + fbeOffset + 4 + fbeBytesSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4 + fbeBytesSize) > _buffer.size)
            return 4

        write(fbeOffset, fbeBytesSize.toUInt())
        write(fbeOffset + 4, value)
        return 4 + fbeBytesSize
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelString(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "FinalModelString.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding string final model
class FinalModelString(buffer: Buffer, offset: Long) : FinalModel(buffer, offset)
{
    // Get the allocation size
    fun fbeAllocationSize(value: String): Long = 4 + 3 * (value.length.toLong() + 1)

    // Check if the string value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return Long.MAX_VALUE

        val fbeStringSize = readUInt32(fbeOffset).toLong()
        if ((_buffer.offset + fbeOffset + 4 + fbeStringSize) > _buffer.size)
            return Long.MAX_VALUE

        return 4 + fbeStringSize
    }

    // Get the string value
    fun get(size: Size): String
    {
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
        {
            size.value = 0
            return ""
        }

        val fbeStringSize = readUInt32(fbeOffset).toLong()
        assert((_buffer.offset + fbeOffset + 4 + fbeStringSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4 + fbeStringSize) > _buffer.size)
        {
            size.value = 4
            return ""
        }

        size.value = 4 + fbeStringSize
        return readString(fbeOffset + 4, fbeStringSize)
    }

    // Set the string value
    fun set(value: String): Long
    {
        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        val bytes = value.toByteArray(java.nio.charset.StandardCharsets.UTF_8)

        val fbeStringSize = bytes.size.toLong()
        assert((_buffer.offset + fbeOffset + 4 + fbeStringSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4 + fbeStringSize) > _buffer.size)
            return 4

        write(fbeOffset, fbeStringSize.toUInt())
        write(fbeOffset + 4, bytes)
        return 4 + fbeStringSize
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelOptional(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelOptional" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding optional _NAME_ final model
class FinalModelOptional_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FinalModel(buffer, offset)
{
    // Get the allocation size
    fun fbeAllocationSize(optional: _TYPE_): Long = 1 + (if (optional != null) value.fbeAllocationSize(optional) else 0)

    // Checks if the object contains a value
    fun hasValue(): Boolean
    {
        if ((_buffer.offset + fbeOffset + 1) > _buffer.size)
            return false

        val fbeHasValue = readInt8(fbeOffset).toInt()
        return fbeHasValue != 0
    }

    // Base final model value
    val value = _MODEL_(buffer, 0)

    // Check if the optional value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + 1) > _buffer.size)
            return Long.MAX_VALUE

        val fbeHasValue = readInt8(fbeOffset).toInt()
        if (fbeHasValue == 0)
            return 1

        _buffer.shift(fbeOffset + 1)
        val fbeResult = value.verify()
        _buffer.unshift(fbeOffset + 1)
        return 1 + fbeResult
    }

    // Get the optional value
    fun get(size: _DOMAIN_fbe.Size): _TYPE_
    {
        assert((_buffer.offset + fbeOffset + 1) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 1) > _buffer.size)
        {
            size.value = 0
            return null
        }

        if (!hasValue())
        {
            size.value = 1
            return null
        }

        _buffer.shift(fbeOffset + 1)
        val optional = value.get(size)
        _buffer.unshift(fbeOffset + 1)
        size.value += 1
        return optional
    }

    // Set the optional value
    fun set(optional: _TYPE_): Long
    {
        assert((_buffer.offset + fbeOffset + 1) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 1) > _buffer.size)
            return 0

        val fbeHasValue = if (optional != null) 1 else 0
        write(fbeOffset, fbeHasValue.toByte())
        if (fbeHasValue == 0)
            return 1

        _buffer.shift(fbeOffset + 1)
        val size = value.set(optional!!)
        _buffer.unshift(fbeOffset + 1)
        return 1 + size
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelArray(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& base, bool optional, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelArray" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ array final model
class FinalModelArray_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long, private val _size: Long) : _DOMAIN_fbe.FinalModel(buffer, offset)
{
    private val _model = _MODEL_(buffer, offset)

    // Get the allocation size
    fun fbeAllocationSize(values: _ARRAY_): Long
    {
        var size: Long = 0
        var i: Long = 0
        while ((i < values.size) && (i < _size))
        {
            size += _model.fbeAllocationSize(values[i.toInt()])
            i++
        }
        return size
    }
    fun fbeAllocationSize(values: java.util.ArrayList<_TYPE_>): Long
    {
        var size: Long = 0
        var i: Long = 0
        while ((i < values.size) && (i < _size))
        {
            size += _model.fbeAllocationSize(values[i.toInt()])
            i++
        }
        return size
    }

    // Check if the array is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset) > _buffer.size)
            return Long.MAX_VALUE

        var size: Long = 0
        _model.fbeOffset = fbeOffset
        var i = _size
        while (i-- > 0)
        {
            val offset = _model.verify()
            if (offset == Long.MAX_VALUE)
                return Long.MAX_VALUE
            _model.fbeShift(offset)
            size += offset
        }
        return size
    }

    // Get the array
    fun get(size: _DOMAIN_fbe.Size): _ARRAY_
    {
        val values = _INIT_

        assert((_buffer.offset + fbeOffset) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset) > _buffer.size)
        {
            size.value = 0
            return values
        }

        size.value = 0
        val offset = _DOMAIN_fbe.Size()
        _model.fbeOffset = fbeOffset
        for (i in 0 until _size)
        {
            offset.value = 0
            values[i.toInt()] = _model.get(offset)
            _model.fbeShift(offset.value)
            size.value += offset.value
        }
        return values
    }

    // Get the array
    fun get(values: _ARRAY_): Long
    {
        assert((_buffer.offset + fbeOffset) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset) > _buffer.size)
            return 0

        var size: Long = 0
        val offset = _DOMAIN_fbe.Size()
        _model.fbeOffset = fbeOffset
        var i: Long = 0
        while ((i < values.size) && (i < _size))
        {
            offset.value = 0
            values[i.toInt()] = _model.get(offset)
            _model.fbeShift(offset.value)
            size += offset.value
            i++
        }
        return size
    }

    // Get the array as java.util.ArrayList
    fun get(values: java.util.ArrayList<_TYPE_>): Long
    {
        values.clear()

        assert((_buffer.offset + fbeOffset) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset) > _buffer.size)
            return 0

        values.ensureCapacity(_size.toInt())

        var size: Long = 0
        val offset = _DOMAIN_fbe.Size()
        _model.fbeOffset = fbeOffset
        var i = _size
        while (i-- > 0)
        {
            offset.value = 0
            val value = _model.get(offset)
            values.add(value)
            _model.fbeShift(offset.value)
            size += offset.value
        }
        return size
    }

    // Set the array
    fun set(values: _ARRAY_): Long
    {
        assert((_buffer.offset + fbeOffset) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset) > _buffer.size)
            return 0

        var size: Long = 0
        _model.fbeOffset = fbeOffset
        var i: Long = 0
        while ((i < values.size) && (i < _size))
        {
            val offset = _model.set(values[i.toInt()])
            _model.fbeShift(offset)
            size += offset
            i++
        }
        return size
    }

    // Set the array as java.util.ArrayList
    fun set(values: java.util.ArrayList<_TYPE_>): Long
    {
        assert((_buffer.offset + fbeOffset) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset) > _buffer.size)
            return 0

        var size: Long = 0
        _model.fbeOffset = fbeOffset
        var i: Long = 0
        while ((i < values.size) && (i < _size))
        {
            val offset = _model.set(values[i.toInt()])
            _model.fbeShift(offset)
            size += offset
            i++
        }
        return size
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("_ARRAY_"), "Array<" + type_name + ">");
    if (optional)
        code = std::regex_replace(code, std::regex("_INIT_"), "arrayOfNulls<" + type_name + ">(_size.toInt())");
    else
        code = std::regex_replace(code, std::regex("_INIT_"), "Array(_size.toInt()) { " + ConvertDefault(domain, package, base) + " }");
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelVector(const std::string& domain, const std::string& package, const std::string& name, const std::string& type, const std::string& model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelVector" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ vector final model
class FinalModelVector_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FinalModel(buffer, offset)
{
    private val _model = _MODEL_(buffer, offset)

    // Get the allocation size
    fun fbeAllocationSize(values: java.util.ArrayList<_TYPE_>): Long
    {
        var size: Long = 4
        for (value in values)
            size += _model.fbeAllocationSize(value)
        return size
    }
    fun fbeAllocationSize(values: java.util.LinkedList<_TYPE_>): Long
    {
        var size: Long = 4
        for (value in values)
            size += _model.fbeAllocationSize(value)
        return size
    }
    fun fbeAllocationSize(values: java.util.HashSet<_TYPE_>): Long
    {
        var size: Long = 4
        for (value in values)
            size += _model.fbeAllocationSize(value)
        return size
    }

    // Check if the vector is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return Long.MAX_VALUE

        val fbeVectorSize = readUInt32(fbeOffset).toLong()

        var size: Long = 4
        _model.fbeOffset = fbeOffset + 4
        var i = fbeVectorSize
        while (i-- > 0)
        {
            val offset = _model.verify()
            if (offset == Long.MAX_VALUE)
                return Long.MAX_VALUE
            _model.fbeShift(offset)
            size += offset
        }
        return size
    }

    // Get the vector as java.util.ArrayList
    fun get(values: java.util.ArrayList<_TYPE_>): Long
    {
        values.clear()

        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        val fbeVectorSize = readUInt32(fbeOffset).toLong()
        if (fbeVectorSize == 0L)
            return 4

        values.ensureCapacity(fbeVectorSize.toInt())

        var size: Long = 4
        val offset = _DOMAIN_fbe.Size()
        _model.fbeOffset = fbeOffset + 4
        for (i in 0 until fbeVectorSize)
        {
            offset.value = 0
            val value = _model.get(offset)
            values.add(value)
            _model.fbeShift(offset.value)
            size += offset.value
        }
        return size
    }

    // Get the vector as java.util.LinkedList
    fun get(values: java.util.LinkedList<_TYPE_>): Long
    {
        values.clear()

        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        val fbeVectorSize = readUInt32(fbeOffset).toLong()
        if (fbeVectorSize == 0L)
            return 4

        var size: Long = 4
        val offset = _DOMAIN_fbe.Size()
        _model.fbeOffset = fbeOffset + 4
        for (i in 0 until fbeVectorSize)
        {
            offset.value = 0
            val value = _model.get(offset)
            values.add(value)
            _model.fbeShift(offset.value)
            size += offset.value
        }
        return size
    }

    // Get the vector as java.util.HashSet
    fun get(values: java.util.HashSet<_TYPE_>): Long
    {
        values.clear()

        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        val fbeVectorSize = readUInt32(fbeOffset).toLong()
        if (fbeVectorSize == 0L)
            return 4

        var size: Long = 4
        val offset = _DOMAIN_fbe.Size()
        _model.fbeOffset = fbeOffset + 4
        for (i in 0 until fbeVectorSize)
        {
            offset.value = 0
            val value = _model.get(offset)
            values.add(value)
            _model.fbeShift(offset.value)
            size += offset.value
        }
        return size
    }

    // Set the vector as java.util.ArrayList
    fun set(values: java.util.ArrayList<_TYPE_>): Long
    {
        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        write(fbeOffset, values.size.toUInt())

        var size: Long = 4
        _model.fbeOffset = fbeOffset + 4
        for (value in values)
        {
            val offset = _model.set(value)
            _model.fbeShift(offset)
            size += offset
        }
        return size
    }

    // Set the vector as java.util.LinkedList
    fun set(values: java.util.LinkedList<_TYPE_>): Long
    {
        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        write(fbeOffset, values.size.toUInt())

        var size: Long = 4
        _model.fbeOffset = fbeOffset + 4
        for (value in values)
        {
            val offset = _model.set(value)
            _model.fbeShift(offset)
            size += offset
        }
        return size
    }

    // Set the vector as java.util.HashSet
    fun set(values: java.util.HashSet<_TYPE_>): Long
    {
        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        write(fbeOffset, values.size.toUInt())

        var size: Long = 4
        _model.fbeOffset = fbeOffset + 4
        for (value in values)
        {
            val offset = _model.set(value)
            _model.fbeShift(offset)
            size += offset
        }
        return size
    }
}
)CODE";

    std::string type_name = IsPackageType(type) ? type : (domain + package + "." + type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_TYPE_"), type_name);
    code = std::regex_replace(code, std::regex("_MODEL_"), model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelMap(const std::string& domain, const std::string& package, const std::string& key_name, const std::string& key_type, const std::string& key_model, const std::string& value_name, const std::string& value_type, const std::string& value_model)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModelMap" + key_name + value_name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _KEY_NAME_->_VALUE_NAME_ map final model
class FinalModelMap_KEY_NAME__VALUE_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FinalModel(buffer, offset)
{
    private val _modelKey = _KEY_MODEL_(buffer, offset)
    private val _modelValue = _VALUE_MODEL_(buffer, offset)

    // Get the allocation size
    fun fbeAllocationSize(values: java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_>): Long
    {
        var size: Long = 4
        for ((key, value1) in values)
        {
            size += _modelKey.fbeAllocationSize(key)
            size += _modelValue.fbeAllocationSize(value1)
        }
        return size
    }
    fun fbeAllocationSize(values: java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_>): Long
    {
        var size: Long = 4
        for ((key, value1) in values)
        {
            size += _modelKey.fbeAllocationSize(key)
            size += _modelValue.fbeAllocationSize(value1)
        }
        return size
    }

    // Check if the map is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return Long.MAX_VALUE

        val fbeMapSize = readUInt32(fbeOffset).toLong()

        var size: Long = 4
        _modelKey.fbeOffset = fbeOffset + 4
        _modelValue.fbeOffset = fbeOffset + 4
        var i = fbeMapSize
        while (i-- > 0)
        {
            val offsetKey = _modelKey.verify()
            if (offsetKey == Long.MAX_VALUE)
                return Long.MAX_VALUE
            _modelKey.fbeShift(offsetKey)
            _modelValue.fbeShift(offsetKey)
            size += offsetKey
            val offsetValue = _modelValue.verify()
            if (offsetValue == Long.MAX_VALUE)
                return Long.MAX_VALUE
            _modelKey.fbeShift(offsetValue)
            _modelValue.fbeShift(offsetValue)
            size += offsetValue
        }
        return size
    }

    // Get the map as java.util.TreeMap
    fun get(values: java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_>): Long
    {
        values.clear()

        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        val fbeMapSize = readUInt32(fbeOffset).toLong()
        if (fbeMapSize == 0L)
            return 4

        var size: Long = 4
        val offset = _DOMAIN_fbe.Size()
        _modelKey.fbeOffset = fbeOffset + 4
        _modelValue.fbeOffset = fbeOffset + 4
        var i = fbeMapSize
        while (i-- > 0)
        {
            offset.value = 0
            val key = _modelKey.get(offset)
            _modelKey.fbeShift(offset.value)
            _modelValue.fbeShift(offset.value)
            size += offset.value
            offset.value = 0
            val value = _modelValue.get(offset)
            _modelKey.fbeShift(offset.value)
            _modelValue.fbeShift(offset.value)
            size += offset.value
            values[key] = value
        }
        return size
    }

    // Get the map as java.util.HashMap
    fun get(values: java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_>): Long
    {
        values.clear()

        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        val fbeMapSize = readUInt32(fbeOffset).toLong()
        if (fbeMapSize == 0L)
            return 4

        var size: Long = 4
        val offset = _DOMAIN_fbe.Size()
        _modelKey.fbeOffset = fbeOffset + 4
        _modelValue.fbeOffset = fbeOffset + 4
        var i = fbeMapSize
        while (i-- > 0)
        {
            offset.value = 0
            val key = _modelKey.get(offset)
            _modelKey.fbeShift(offset.value)
            _modelValue.fbeShift(offset.value)
            size += offset.value
            offset.value = 0
            val value = _modelValue.get(offset)
            _modelKey.fbeShift(offset.value)
            _modelValue.fbeShift(offset.value)
            size += offset.value

            values[key] = value
        }
        return size
    }

    // Set the map as java.util.TreeMap
    fun set(values: java.util.TreeMap<_KEY_TYPE_, _VALUE_TYPE_>): Long
    {
        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        write(fbeOffset, values.size.toUInt())

        var size: Long = 4
        _modelKey.fbeOffset = fbeOffset + 4
        _modelValue.fbeOffset = fbeOffset + 4
        for ((key, value1) in values)
        {
            val offsetKey = _modelKey.set(key)
            _modelKey.fbeShift(offsetKey)
            _modelValue.fbeShift(offsetKey)
            val offsetValue = _modelValue.set(value1)
            _modelKey.fbeShift(offsetValue)
            _modelValue.fbeShift(offsetValue)
            size += offsetKey + offsetValue
        }
        return size
    }

    // Set the map as java.util.HashMap
    fun set(values: java.util.HashMap<_KEY_TYPE_, _VALUE_TYPE_>): Long
    {
        assert((_buffer.offset + fbeOffset + 4) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + 4) > _buffer.size)
            return 0

        write(fbeOffset, values.size.toUInt())

        var size: Long = 4
        _modelKey.fbeOffset = fbeOffset + 4
        _modelValue.fbeOffset = fbeOffset + 4
        for ((key, value1) in values)
        {
            val offsetKey = _modelKey.set(key)
            _modelKey.fbeShift(offsetKey)
            _modelValue.fbeShift(offsetKey)
            val offsetValue = _modelValue.set(value1)
            _modelKey.fbeShift(offsetValue)
            _modelValue.fbeShift(offsetValue)
            size += offsetKey + offsetValue
        }
        return size
    }
}
)CODE";

    std::string key_type_name = IsPackageType(key_type) ? key_type : (domain + package + "." + key_type);
    std::string value_type_name = IsPackageType(value_type) ? value_type : (domain + package + "." + value_type);

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_KEY_NAME_"), key_name);
    code = std::regex_replace(code, std::regex("_KEY_TYPE_"), key_type_name);
    code = std::regex_replace(code, std::regex("_KEY_MODEL_"), key_model);
    code = std::regex_replace(code, std::regex("_VALUE_NAME_"), value_name);
    code = std::regex_replace(code, std::regex("_VALUE_TYPE_"), value_type_name);
    code = std::regex_replace(code, std::regex("_VALUE_MODEL_"), value_model);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEFinalModelEnumFlags(const std::string& domain, const std::string& package, const std::string& name, const std::string& type)
{
    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModel" + name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string code = R"CODE(
// Fast Binary Encoding _NAME_ final model
class FinalModel_NAME_(buffer: _DOMAIN_fbe.Buffer, offset: Long) : _DOMAIN_fbe.FinalModel(buffer, offset)
{
    // Get the allocation size
    @Suppress("UNUSED_PARAMETER")
    fun fbeAllocationSize(value: _DOMAIN__PACKAGE_._NAME_): Long = fbeSize

    // Final size
    override val fbeSize: Long = _SIZE_

    // Check if the value is valid
    override fun verify(): Long
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return Long.MAX_VALUE

        return fbeSize
    }

    // Get the value
    fun get(size: _DOMAIN_fbe.Size): _DOMAIN__PACKAGE_._NAME_
    {
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return _DOMAIN__PACKAGE_._NAME_()

        size.value = fbeSize
        return _DOMAIN__PACKAGE_._NAME_(_READ_(fbeOffset))
    }

    // Set the value
    fun set(value: _DOMAIN__PACKAGE_._NAME_): Long
    {
        assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { "Model is broken!" }
        if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)
            return 0

        write(fbeOffset, value.raw)
        return fbeSize
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("_DOMAIN_"), domain);
    code = std::regex_replace(code, std::regex("_PACKAGE_"), package);
    code = std::regex_replace(code, std::regex("_NAME_"), name);
    code = std::regex_replace(code, std::regex("_SIZE_"), ConvertEnumSize(type));
    code = std::regex_replace(code, std::regex("_READ_"), ConvertEnumRead(type));
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBESender(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Sender.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base sender
@Suppress("MemberVisibilityCanBePrivate")
abstract class Sender : ISenderListener
{
    // Get the bytes buffer
    var buffer: Buffer = Buffer()
        private set
    // Enable/Disable logging
    var logging: Boolean = false
    // Get the final protocol flag
    var final: Boolean = false
        private set

    protected constructor(final: Boolean) { this.final = final }
    protected constructor(buffer: Buffer, final: Boolean) { this.buffer = buffer; this.final = final }

    // Reset the sender buffer
    fun reset() { buffer.reset() }

    // Send serialized buffer.
    // Direct call of the method requires knowledge about internals of FBE models serialization.
    // Use it with care!
    fun sendSerialized(listener: ISenderListener, serialized: Long): Long
    {
        assert(serialized > 0) { "Invalid size of the serialized buffer!" }
        if (serialized <= 0)
            return 0

        // Shift the send buffer
        buffer.shift(serialized)

        // Send the value
        val sent = listener.onSend(buffer.data, 0, buffer.size)
        buffer.remove(0, sent)
        return sent
    }
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBESenderListener(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "ISenderListener.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base sender listener interface
interface ISenderListener
{
    // Send message handler
    fun onSend(buffer: ByteArray, offset: Long, size: Long): Long { return size }
    // Send log message handler
    fun onSendLog(message: String) {}
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEReceiver(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Receiver.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base receiver
@Suppress("MemberVisibilityCanBePrivate")
abstract class Receiver : IReceiverListener
{
    // Get the bytes buffer
    var buffer: Buffer = Buffer()
        private set
    // Enable/Disable logging
    var logging: Boolean = false
    // Get the final protocol flag
    var final: Boolean = false
        private set

    protected constructor(final: Boolean) { this.final = final }
    protected constructor(buffer: Buffer, final: Boolean) { this.buffer = buffer; this.final = final }

    // Reset the receiver buffer
    fun reset() { buffer.reset() }

    // Receive data
    fun receive(buffer: Buffer) { receive(buffer.data, 0, buffer.size) }
    fun receive(buffer: ByteArray, offset: Long = 0, size: Long = buffer.size.toLong())
    {
        assert((offset + size) <= buffer.size) { "Invalid offset & size!" }
        if ((offset + size) > buffer.size)
            throw IllegalArgumentException("Invalid offset & size!")

        if (size == 0L)
            return

        // Storage buffer
        var offset0 = this.buffer.offset
        var offset1 = this.buffer.size
        var size1 = this.buffer.size

        // Receive buffer
        var offset2: Long = 0

        // While receive buffer is available to handle...
        while (offset2 < size)
        {
            var messageBuffer: ByteArray? = null
            var messageOffset: Long = 0
            var messageSize: Long = 0

            // Try to receive message size
            var messageSizeCopied = false
            var messageSizeFound = false
            while (!messageSizeFound)
            {
                // Look into the storage buffer
                if (offset0 < size1)
                {
                    var count = kotlin.math.min(size1 - offset0, 4)
                    if (count == 4L)
                    {
                        messageSizeCopied = true
                        messageSizeFound = true
                        messageSize = Buffer.readUInt32(this.buffer.data, offset0).toLong()
                        offset0 += 4
                        break
                    }
                    else
                    {
                        // Fill remaining data from the receive buffer
                        if (offset2 < size)
                        {
                            count = kotlin.math.min(size - offset2, 4 - count)

                            // Allocate and refresh the storage buffer
                            this.buffer.allocate(count)
                            size1 += count

                            System.arraycopy(buffer, (offset + offset2).toInt(), this.buffer.data, offset1.toInt(), count.toInt())
                            offset1 += count
                            offset2 += count
                            continue
                        }
                        else
                            break
                    }
                }

                // Look into the receive buffer
                if (offset2 < size)
                {
                    val count = kotlin.math.min(size - offset2, 4)
                    if (count == 4L)
                    {
                        messageSizeFound = true
                        messageSize = Buffer.readUInt32(buffer, offset + offset2).toLong()
                        offset2 += 4
                        break
                    }
                    else
                    {
                        // Allocate and refresh the storage buffer
                        this.buffer.allocate(count)
                        size1 += count

                        System.arraycopy(buffer, (offset + offset2).toInt(), this.buffer.data, offset1.toInt(), count.toInt())
                        offset1 += count
                        offset2 += count
                        continue
                    }
                }
                else
                    break
            }

            if (!messageSizeFound)
                return

            // Check the message full size
            val minSize = if (final) (4 + 4) else (4 + 4 + 4 + 4)
            assert(messageSize >= minSize) { "Invalid receive data!" }
            if (messageSize < minSize)
                return

            // Try to receive message body
            var messageFound = false
            while (!messageFound)
            {
                // Look into the storage buffer
                if (offset0 < size1)
                {
                    var count = kotlin.math.min(size1 - offset0, messageSize - 4)
                    if (count == (messageSize - 4))
                    {
                        messageFound = true
                        messageBuffer = this.buffer.data
                        messageOffset = offset0 - 4
                        offset0 += messageSize - 4
                        break
                    }
                    else
                    {
                        // Fill remaining data from the receive buffer
                        if (offset2 < size)
                        {
                            // Copy message size into the storage buffer
                            if (!messageSizeCopied)
                            {
                                // Allocate and refresh the storage buffer
                                this.buffer.allocate(4)
                                size1 += 4

                                Buffer.write(this.buffer.data, offset0, messageSize.toUInt())
                                offset0 += 4
                                offset1 += 4

                                messageSizeCopied = true
                            }

                            count = kotlin.math.min(size - offset2, messageSize - 4 - count)

                            // Allocate and refresh the storage buffer
                            this.buffer.allocate(count)
                            size1 += count

                            System.arraycopy(buffer, (offset + offset2).toInt(), this.buffer.data, offset1.toInt(), count.toInt())
                            offset1 += count
                            offset2 += count
                            continue
                        }
                        else
                            break
                    }
                }

                // Look into the receive buffer
                if (offset2 < size)
                {
                    val count = kotlin.math.min(size - offset2, messageSize - 4)
                    if (!messageSizeCopied && (count == (messageSize - 4)))
                    {
                        messageFound = true
                        messageBuffer = buffer
                        messageOffset = offset + offset2 - 4
                        offset2 += messageSize - 4
                        break
                    }
                    else
                    {
                        // Copy message size into the storage buffer
                        if (!messageSizeCopied)
                        {
                            // Allocate and refresh the storage buffer
                            this.buffer.allocate(4)
                            size1 += 4

                            Buffer.write(this.buffer.data, offset0, messageSize.toUInt())
                            offset0 += 4
                            offset1 += 4

                            messageSizeCopied = true
                        }

                        // Allocate and refresh the storage buffer
                        this.buffer.allocate(count)
                        size1 += count

                        System.arraycopy(buffer, (offset + offset2).toInt(), this.buffer.data, offset1.toInt(), count.toInt())
                        offset1 += count
                        offset2 += count
                        continue
                    }
                }
                else
                    break
            }

            if (!messageFound)
            {
                // Copy message size into the storage buffer
                if (!messageSizeCopied)
                {
                    // Allocate and refresh the storage buffer
                    this.buffer.allocate(4)
                    size1 += 4

                    Buffer.write(this.buffer.data, offset0, messageSize.toUInt())
                    offset0 += 4
                    offset1 += 4

                    @Suppress("UNUSED_VALUE")
                    messageSizeCopied = true
                }
                return
            }

            if (messageBuffer != null)
            {
                @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
                val fbeStructSize: Long
                val fbeStructType: Long

                // Read the message parameters
                if (final)
                {
                    @Suppress("UNUSED_VALUE")
                    fbeStructSize = Buffer.readUInt32(messageBuffer, messageOffset).toLong()
                    fbeStructType = Buffer.readUInt32(messageBuffer, messageOffset + 4).toLong()
                }
                else
                {
                    val fbeStructOffset = Buffer.readUInt32(messageBuffer, messageOffset + 4).toLong()
                    @Suppress("UNUSED_VALUE")
                    fbeStructSize = Buffer.readUInt32(messageBuffer, messageOffset + fbeStructOffset).toLong()
                    fbeStructType = Buffer.readUInt32(messageBuffer, messageOffset + fbeStructOffset + 4).toLong()
                }

                // Handle the message
                onReceive(fbeStructType, messageBuffer, messageOffset, messageSize)
            }

            // Reset the storage buffer
            this.buffer.reset()

            // Refresh the storage buffer
            offset0 = this.buffer.offset
            offset1 = this.buffer.size
            size1 = this.buffer.size
        }
    }

    // Receive message handler
    abstract fun onReceive(type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEReceiverListener(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "IReceiverListener.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base receiver listener interface
interface IReceiverListener
{
    // Receive log message handler
    fun onReceiveLog(message: String) {}
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEClient(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Client.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base client
@Suppress("MemberVisibilityCanBePrivate")
abstract class Client : IClientListener
{
    // Get the send bytes buffer
    var sendBuffer: Buffer = Buffer()
        private set
    // Get the receive bytes buffer
    var receiveBuffer: Buffer = Buffer()
        private set
    // Enable/Disable logging
    var logging: Boolean = false
    // Get the final protocol flag
    var final: Boolean = false
        private set

    protected constructor(final: Boolean) { this.final = final }
    protected constructor(sendBuffer: Buffer, receiveBuffer: Buffer, final: Boolean) { this.sendBuffer = sendBuffer; this.receiveBuffer = receiveBuffer; this.final = final }

    // Reset the client buffers
    fun reset() { sendBuffer.reset(); receiveBuffer.reset() }

    // Send serialized buffer.
    // Direct call of the method requires knowledge about internals of FBE models serialization.
    // Use it with care!
    fun sendSerialized(listener: ISenderListener, serialized: Long): Long
    {
        assert(serialized > 0) { "Invalid size of the serialized buffer!" }
        if (serialized <= 0)
            return 0

        // Shift the send buffer
        sendBuffer.shift(serialized)

        // Send the value
        val sent = listener.onSend(sendBuffer.data, 0, sendBuffer.size)
        sendBuffer.remove(0, sent)
        return sent
    }

    // Receive data
    fun receive(buffer: Buffer) { receive(buffer.data, 0, buffer.size) }
    fun receive(buffer: ByteArray, offset: Long = 0, size: Long = buffer.size.toLong())
    {
        assert((offset + size) <= buffer.size) { "Invalid offset & size!" }
        if ((offset + size) > buffer.size)
            throw IllegalArgumentException("Invalid offset & size!")

        if (size == 0L)
            return

        // Storage buffer
        var offset0 = this.receiveBuffer.offset
        var offset1 = this.receiveBuffer.size
        var size1 = this.receiveBuffer.size

        // Receive buffer
        var offset2: Long = 0

        // While receive buffer is available to handle...
        while (offset2 < size)
        {
            var messageBuffer: ByteArray? = null
            var messageOffset: Long = 0
            var messageSize: Long = 0

            // Try to receive message size
            var messageSizeCopied = false
            var messageSizeFound = false
            while (!messageSizeFound)
            {
                // Look into the storage buffer
                if (offset0 < size1)
                {
                    var count = kotlin.math.min(size1 - offset0, 4)
                    if (count == 4L)
                    {
                        messageSizeCopied = true
                        messageSizeFound = true
                        messageSize = Buffer.readUInt32(this.receiveBuffer.data, offset0).toLong()
                        offset0 += 4
                        break
                    }
                    else
                    {
                        // Fill remaining data from the receive buffer
                        if (offset2 < size)
                        {
                            count = kotlin.math.min(size - offset2, 4 - count)

                            // Allocate and refresh the storage buffer
                            this.receiveBuffer.allocate(count)
                            size1 += count

                            System.arraycopy(buffer, (offset + offset2).toInt(), this.receiveBuffer.data, offset1.toInt(), count.toInt())
                            offset1 += count
                            offset2 += count
                            continue
                        }
                        else
                            break
                    }
                }

                // Look into the receive buffer
                if (offset2 < size)
                {
                    val count = kotlin.math.min(size - offset2, 4)
                    if (count == 4L)
                    {
                        messageSizeFound = true
                        messageSize = Buffer.readUInt32(buffer, offset + offset2).toLong()
                        offset2 += 4
                        break
                    }
                    else
                    {
                        // Allocate and refresh the storage buffer
                        this.receiveBuffer.allocate(count)
                        size1 += count

                        System.arraycopy(buffer, (offset + offset2).toInt(), this.receiveBuffer.data, offset1.toInt(), count.toInt())
                        offset1 += count
                        offset2 += count
                        continue
                    }
                }
                else
                    break
            }

            if (!messageSizeFound)
                return

            // Check the message full size
            val minSize = if (final) (4 + 4) else (4 + 4 + 4 + 4)
            assert(messageSize >= minSize) { "Invalid receive data!" }
            if (messageSize < minSize)
                return

            // Try to receive message body
            var messageFound = false
            while (!messageFound)
            {
                // Look into the storage buffer
                if (offset0 < size1)
                {
                    var count = kotlin.math.min(size1 - offset0, messageSize - 4)
                    if (count == (messageSize - 4))
                    {
                        messageFound = true
                        messageBuffer = this.receiveBuffer.data
                        messageOffset = offset0 - 4
                        offset0 += messageSize - 4
                        break
                    }
                    else
                    {
                        // Fill remaining data from the receive buffer
                        if (offset2 < size)
                        {
                            // Copy message size into the storage buffer
                            if (!messageSizeCopied)
                            {
                                // Allocate and refresh the storage buffer
                                this.receiveBuffer.allocate(4)
                                size1 += 4

                                Buffer.write(this.receiveBuffer.data, offset0, messageSize.toUInt())
                                offset0 += 4
                                offset1 += 4

                                messageSizeCopied = true
                            }

                            count = kotlin.math.min(size - offset2, messageSize - 4 - count)

                            // Allocate and refresh the storage buffer
                            this.receiveBuffer.allocate(count)
                            size1 += count

                            System.arraycopy(buffer, (offset + offset2).toInt(), this.receiveBuffer.data, offset1.toInt(), count.toInt())
                            offset1 += count
                            offset2 += count
                            continue
                        }
                        else
                            break
                    }
                }

                // Look into the receive buffer
                if (offset2 < size)
                {
                    val count = kotlin.math.min(size - offset2, messageSize - 4)
                    if (!messageSizeCopied && (count == (messageSize - 4)))
                    {
                        messageFound = true
                        messageBuffer = buffer
                        messageOffset = offset + offset2 - 4
                        offset2 += messageSize - 4
                        break
                    }
                    else
                    {
                        // Copy message size into the storage buffer
                        if (!messageSizeCopied)
                        {
                            // Allocate and refresh the storage buffer
                            this.receiveBuffer.allocate(4)
                            size1 += 4

                            Buffer.write(this.receiveBuffer.data, offset0, messageSize.toUInt())
                            offset0 += 4
                            offset1 += 4

                            messageSizeCopied = true
                        }

                        // Allocate and refresh the storage buffer
                        this.receiveBuffer.allocate(count)
                        size1 += count

                        System.arraycopy(buffer, (offset + offset2).toInt(), this.receiveBuffer.data, offset1.toInt(), count.toInt())
                        offset1 += count
                        offset2 += count
                        continue
                    }
                }
                else
                    break
            }

            if (!messageFound)
            {
                // Copy message size into the storage buffer
                if (!messageSizeCopied)
                {
                    // Allocate and refresh the storage buffer
                    this.receiveBuffer.allocate(4)
                    size1 += 4

                    Buffer.write(this.receiveBuffer.data, offset0, messageSize.toUInt())
                    offset0 += 4
                    offset1 += 4

                    @Suppress("UNUSED_VALUE")
                    messageSizeCopied = true
                }
                return
            }

            if (messageBuffer != null)
            {
                @Suppress("ASSIGNED_BUT_NEVER_ACCESSED_VARIABLE")
                val fbeStructSize: Long
                val fbeStructType: Long

                // Read the message parameters
                if (final)
                {
                    @Suppress("UNUSED_VALUE")
                    fbeStructSize = Buffer.readUInt32(messageBuffer, messageOffset).toLong()
                    fbeStructType = Buffer.readUInt32(messageBuffer, messageOffset + 4).toLong()
                }
                else
                {
                    val fbeStructOffset = Buffer.readUInt32(messageBuffer, messageOffset + 4).toLong()
                    @Suppress("UNUSED_VALUE")
                    fbeStructSize = Buffer.readUInt32(messageBuffer, messageOffset + fbeStructOffset).toLong()
                    fbeStructType = Buffer.readUInt32(messageBuffer, messageOffset + fbeStructOffset + 4).toLong()
                }

                // Handle the message
                onReceive(fbeStructType, messageBuffer, messageOffset, messageSize)
            }

            // Reset the storage buffer
            this.receiveBuffer.reset()

            // Refresh the storage buffer
            offset0 = this.receiveBuffer.offset
            offset1 = this.receiveBuffer.size
            size1 = this.receiveBuffer.size
        }
    }

    // Receive message handler
    abstract fun onReceive(type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEClientListener(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "IClientListener.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
// Fast Binary Encoding base client listener interface
interface IClientListener : ISenderListener, IReceiverListener
{
}
)CODE";

    // Prepare code template
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateFBEJson(const std::string& domain, const std::string& package)
{
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, package);

    // Generate the file
    CppCommon::Path file = path / "Json.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader("FBE");
    GenerateImports(domain, package);

    std::string code = R"CODE(
internal class BytesJson : com.google.gson.JsonSerializer<ByteArray>, com.google.gson.JsonDeserializer<ByteArray>
{
    override fun serialize(src: ByteArray, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        return com.google.gson.JsonPrimitive(java.util.Base64.getEncoder().encodeToString(src))
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): ByteArray
    {
        return java.util.Base64.getDecoder().decode(json.asString)
    }
}

internal class CharacterJson : com.google.gson.JsonSerializer<Char>, com.google.gson.JsonDeserializer<Char>
{
    override fun serialize(src: Char, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        return com.google.gson.JsonPrimitive(src.toLong())
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): Char
    {
        return json.asLong.toChar()
    }
}

_TIMESTAMP_CLASS_

internal class BigDecimalJson : com.google.gson.JsonSerializer<java.math.BigDecimal>, com.google.gson.JsonDeserializer<java.math.BigDecimal>
{
    override fun serialize(src: java.math.BigDecimal, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        return com.google.gson.JsonPrimitive(src.toPlainString())
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): java.math.BigDecimal
    {
        return java.math.BigDecimal(json.asJsonPrimitive.asString)
    }
}

internal class UUIDJson : com.google.gson.JsonSerializer<java.util.UUID>, com.google.gson.JsonDeserializer<java.util.UUID>
{
    override fun serialize(src: java.util.UUID, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        return com.google.gson.JsonPrimitive(src.toString())
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): java.util.UUID {
        return java.util.UUID.fromString(json.asJsonPrimitive.asString)
    }
}

internal class UByteNullableJson : com.google.gson.JsonSerializer<UByte?>, com.google.gson.JsonDeserializer<UByte?>
{
    override fun serialize(src: UByte?, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        if (src == null)
            return com.google.gson.JsonNull.INSTANCE

        return com.google.gson.JsonPrimitive(src.toLong())
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): UByte?
    {
        if (json.isJsonNull)
            return null

        return json.asLong.toUByte()
    }
}

internal class UShortNullableJson : com.google.gson.JsonSerializer<UShort?>, com.google.gson.JsonDeserializer<UShort?>
{
    override fun serialize(src: UShort?, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        if (src == null)
            return com.google.gson.JsonNull.INSTANCE

        return com.google.gson.JsonPrimitive(src.toLong())
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): UShort?
    {
        if (json.isJsonNull)
            return null

        return json.asLong.toUShort()
    }
}

internal class UIntNullableJson : com.google.gson.JsonSerializer<UInt?>, com.google.gson.JsonDeserializer<UInt?>
{
    override fun serialize(src: UInt?, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        if (src == null)
            return com.google.gson.JsonNull.INSTANCE

        return com.google.gson.JsonPrimitive(src.toLong())
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): UInt?
    {
        if (json.isJsonNull)
            return null

        return json.asLong.toUInt()
    }
}

internal class ULongNullableJson : com.google.gson.JsonSerializer<ULong?>, com.google.gson.JsonDeserializer<ULong?>
{
    override fun serialize(src: ULong?, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        if (src == null)
            return com.google.gson.JsonNull.INSTANCE

        return com.google.gson.JsonPrimitive(src.toLong())
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): ULong?
    {
        if (json.isJsonNull)
            return null

        return json.asLong.toULong()
    }
}

// Fast Binary Encoding base JSON engine
@Suppress("MemberVisibilityCanBePrivate")
object Json
{
    // Get the JSON engine
    val engine: com.google.gson.Gson = register(com.google.gson.GsonBuilder()).create()

    fun register(builder: com.google.gson.GsonBuilder): com.google.gson.GsonBuilder
    {
        builder.serializeNulls()
        builder.registerTypeAdapter(ByteArray::class.java, BytesJson())
        builder.registerTypeAdapter(Char::class.java, CharacterJson())
        builder.registerTypeAdapter(Character::class.java, CharacterJson())
        _TIMESTAMP_REGISTER_
        builder.registerTypeAdapter(java.math.BigDecimal::class.java, BigDecimalJson())
        builder.registerTypeAdapter(java.util.UUID::class.java, UUIDJson())
        builder.registerTypeAdapter(kotlin.UByte::class.java, UByteNullableJson())
        builder.registerTypeAdapter(kotlin.UShort::class.java, UShortNullableJson())
        builder.registerTypeAdapter(kotlin.UInt::class.java, UIntNullableJson())
        builder.registerTypeAdapter(kotlin.ULong::class.java, ULongNullableJson())
        return builder
    }
}
)CODE";

    std::string timestamp_class;
    std::string timestamp_register;

    if (Version() < 8)
    {
        timestamp_class = R"CODE(internal class DateJson : com.google.gson.JsonSerializer<java.util.Date>, com.google.gson.JsonDeserializer<java.util.Date>
{
    override fun serialize(src: java.util.Date, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        val nanoseconds = src.time * 1000000
        return com.google.gson.JsonPrimitive(nanoseconds)
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): java.util.Date
    {
        val nanoseconds = json.asJsonPrimitive.asLong
        return java.util.Date(nanoseconds / 1000000)
    }
})CODE";
        timestamp_register = "builder.registerTypeAdapter(java.util.Date::class.java, DateJson())";
    }
    else
    {
        timestamp_class = R"CODE(internal class InstantJson : com.google.gson.JsonSerializer<java.time.Instant>, com.google.gson.JsonDeserializer<java.time.Instant>
{
    override fun serialize(src: java.time.Instant, typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement
    {
        val nanoseconds = src.epochSecond * 1000000000 + src.nano
        return com.google.gson.JsonPrimitive(nanoseconds)
    }

    @Throws(com.google.gson.JsonParseException::class)
    override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): java.time.Instant
    {
        val nanoseconds = json.asJsonPrimitive.asLong
        return java.time.Instant.ofEpochSecond(nanoseconds / 1000000000, nanoseconds % 1000000000)
    }
})CODE";
        timestamp_register = "builder.registerTypeAdapter(java.time.Instant::class.java, InstantJson())";
    }

    // Prepare code template
    code = std::regex_replace(code, std::regex("_TIMESTAMP_CLASS_"), timestamp_class);
    code = std::regex_replace(code, std::regex("_TIMESTAMP_REGISTER_"), timestamp_register);
    code = std::regex_replace(code, std::regex("\n"), EndLine());

    Write(code);

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateContainers(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, *p->name);

    // Create package path
    CppCommon::Directory::CreateTree(path);

    if (p->body)
    {
        // Check all structs in the package
        for (const auto& s : p->body->structs)
        {
            if (s->body)
            {
                // Check all fields in the struct
                for (const auto& field : s->body->fields)
                {
                    if (field->array)
                    {
                        if (final)
                            GenerateFBEFinalModelArray(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), *field->type, field->optional, ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                        else
                            GenerateFBEFieldModelArray(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), *field->type, field->optional, ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                    }
                    if (field->vector || field->list || field->set)
                    {
                        if (final)
                            GenerateFBEFinalModelVector(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                        else
                            GenerateFBEFieldModelVector(domain, *p->name, (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                    }
                    if (field->map || field->hash)
                    {
                        if (final)
                            GenerateFBEFinalModelMap(domain, *p->name, ConvertTypeFieldName(*field->key), ConvertTypeFieldType(domain, *field->key, false), ConvertTypeFieldDeclaration(domain, *field->key, false, final), (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                        else
                            GenerateFBEFieldModelMap(domain, *p->name, ConvertTypeFieldName(*field->key), ConvertTypeFieldType(domain, *field->key, false), ConvertTypeFieldDeclaration(domain, *field->key, false, final), (field->optional ? "Optional" : "") + ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, field->optional, final));
                    }
                    if (field->optional)
                    {
                        if (final)
                            GenerateFBEFinalModelOptional(domain, *p->name, ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, false, final));
                        else
                            GenerateFBEFieldModelOptional(domain, *p->name, ConvertTypeFieldName(*field->type), ConvertTypeFieldType(domain, *field->type, field->optional), ConvertTypeFieldDeclaration(domain, *field->type, false, final));
                    }
                }
            }
        }
    }
}

void GeneratorKotlin::GeneratePackage(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    CppCommon::Path path = CppCommon::Path(_output) / CreatePackagePath(domain, *p->name);

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate namespace
    if (p->body)
    {
        // Generate child enums
        for (const auto& child_e : p->body->enums)
            GenerateEnum(p, child_e, path);

        // Generate child flags
        for (const auto& child_f : p->body->flags)
            GenerateFlags(p, child_f, path);

        // Generate child structs
        for (const auto& child_s : p->body->structs)
            GenerateStruct(p, child_s, path);
    }

    // Generate containers
    GenerateContainers(p, false);
    if (Final())
        GenerateContainers(p, true);

    // Generate protocol
    if (Proto())
    {
        // Generate protocol version
        GenerateProtocolVersion(p);

        // Generate sender & receiver
        GenerateSender(p, false);
        GenerateSenderListener(p, false);
        GenerateReceiver(p, false);
        GenerateReceiverListener(p, false);
        GenerateProxy(p, false);
        GenerateProxyListener(p, false);
        GenerateClient(p, false);
        GenerateClientListener(p, false);
        if (Final())
        {
            GenerateSender(p, true);
            GenerateSenderListener(p, true);
            GenerateReceiver(p, true);
            GenerateReceiverListener(p, true);
            GenerateClient(p, true);
            GenerateClientListener(p, true);
        }
    }

    // Generate JSON engine
    if (JSON())
        GenerateJson(p);
}

void GeneratorKotlin::GenerateEnum(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e, const CppCommon::Path& path)
{
    std::string enum_name = *e->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (enum_name + ".kt");
    WriteBegin();

    // Generate enum header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);
    std::string enum_mapping_type = ConvertEnumBase(enum_type);
    std::string enum_to = ConvertEnumTo(enum_type);

    // Generate enum body
    WriteLine();
    WriteLineIndent("@Suppress(\"EnumEntryName\", \"MemberVisibilityCanBePrivate\", \"RemoveRedundantCallsOfConversionMethods\")");
    WriteLineIndent("enum class " + enum_name);
    WriteLineIndent("{");
    Indent(1);
    if (e->body)
    {
        int index = 0;
        bool first = true;
        std::string last = ConvertEnumConstant(enum_type, enum_type, "0", false);
        for (const auto& value : e->body->values)
        {
            WriteIndent(std::string(first ? "" : ", ") + *value->name + "(");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(enum_type, enum_type, *value->value->constant, false);
                    Write(last + " + " + std::to_string(index++) + (IsUnsignedType(enum_type) ? "u" : ""));
                }
                else if (value->value->reference && !value->value->reference->empty())
                {
                    index = 0;
                    last = ConvertEnumConstant(enum_type, "", *value->value->reference, false);
                    Write(last);
                }
            }
            else
                Write(last + " + " + std::to_string(index++) + (IsUnsignedType(enum_type) ? "u" : ""));
            WriteLine(")");
            first = false;
        }
        WriteLineIndent(";");
        WriteLine();
    }

    // Generate enum value
    WriteLineIndent("var raw: " + enum_mapping_type + " = " + ConvertEnumConstant(enum_type, enum_type, "0", false));
    Indent(1);
    WriteLineIndent("private set");
    Indent(-1);

    // Generate enum constructors
    WriteLine();
    if ((enum_type == "char") || (enum_type == "wchar"))
        WriteLineIndent("constructor(value: Char) { this.raw = value" + enum_to + " }");
    if (IsUnsignedType(enum_type))
    {
        WriteLineIndent("constructor(value: UByte) { this.raw = value" + enum_to + " }");
        WriteLineIndent("constructor(value: UShort) { this.raw = value" + enum_to + " }");
        WriteLineIndent("constructor(value: UInt) { this.raw = value" + enum_to + " }");
        WriteLineIndent("constructor(value: ULong) { this.raw = value" + enum_to + " }");
    }
    else
    {
        WriteLineIndent("constructor(value: Byte) { this.raw = value" + enum_to + " }");
        WriteLineIndent("constructor(value: Short) { this.raw = value" + enum_to + " }");
        WriteLineIndent("constructor(value: Int) { this.raw = value" + enum_to + " }");
        WriteLineIndent("constructor(value: Long) { this.raw = value" + enum_to + " }");
    }
    WriteLineIndent("constructor(value: " + enum_name + ") { this.raw = value.raw }");

    // Generate enum toString() method
    WriteLine();
    WriteLineIndent("override fun toString(): String");
    WriteLineIndent("{");
    Indent(1);
    if (e->body && !e->body->values.empty())
    {
        for (const auto& value : e->body->values)
            WriteLineIndent("if (this == " + *value->name + ")" + " return \"" + *value->name + "\"");
        WriteLineIndent("return \"<unknown>\"");
    }
    else
        WriteLineIndent("return \"<empty>\"");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum mapping
    WriteLine();
    WriteLineIndent("companion object");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("private val mapping = java.util.HashMap<" + enum_mapping_type + ", " + enum_name + ">()");
    WriteLine();
    WriteLineIndent("init");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("for (value in " + enum_name + ".values())");
    Indent(1);
    WriteLineIndent("mapping[value.raw] = value");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum mapValue() method
    WriteLine();
    WriteLineIndent("fun mapValue(value: " + enum_mapping_type + "): " + enum_name + "?" +" { return mapping[value] }");

    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate enum footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);

    // Generate enum wrapper class
    GenerateEnumClass(p, e, path);

    // Generate enum JSON adapter
    if (JSON())
        GenerateEnumJson(p, e);

    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";

    // Generate enum field model
    GenerateFBEFieldModelEnumFlags(domain, *p->name, *e->name, enum_type);

    // Generate enum final model
    if (Final())
        GenerateFBEFinalModelEnumFlags(domain, *p->name, *e->name, enum_type);
}

void GeneratorKotlin::GenerateEnumClass(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e, const CppCommon::Path& path)
{
    std::string enum_name = *e->name;
    std::string enum_type_name = *e->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (enum_name + ".kt");
    WriteBegin();

    // Generate enum class header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";
    std::string enum_base_type = ConvertEnumType(enum_type);
    std::string enum_to = ConvertEnumTo(enum_type);

    // Generate enum class body
    WriteLine();
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"RemoveRedundantCallsOfConversionMethods\")");
    WriteLineIndent("class " + enum_name + " : Comparable<" + enum_name + ">");
    WriteLineIndent("{");
    Indent(1);
    if (e->body)
    {
        WriteLineIndent("companion object");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& value : e->body->values)
            WriteLineIndent("val " + *value->name + " = " + enum_name + "(" + enum_type_name + "." + *value->name + ")");
        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }

    // Generate enum class value
    WriteLineIndent("var value: " + enum_type_name + "?" + " = " + enum_type_name + ".values()[0]");
    Indent(1);
    WriteLineIndent("private set");
    Indent(-1);
    WriteLine();

    // Generate enum raw value
    WriteLineIndent("val raw: " + enum_base_type);
    Indent(1);
    WriteLineIndent("get() = value!!.raw");
    Indent(-1);
    WriteLine();

    // Generate enum class constructors
    WriteLineIndent("constructor()");
    WriteLineIndent("constructor(value: " + enum_base_type + ") { setEnum(value) }");
    WriteLineIndent("constructor(value: " + enum_type_name + ") { setEnum(value) }");
    WriteLineIndent("constructor(value: " + enum_name + ") { setEnum(value) }");
    WriteLine();

    // Generate enum class setDefault() method
    WriteLineIndent("fun setDefault() { setEnum(0" + enum_to + ") }");
    WriteLine();

    // Generate enum class setEnum() methods
    WriteLineIndent("fun setEnum(value: " + enum_base_type + ") { this.value = " + enum_type_name + ".mapValue(value) }");
    WriteLineIndent("fun setEnum(value: " + enum_type_name + ") { this.value = value }");
    WriteLineIndent("fun setEnum(value: " + enum_name + ") { this.value = value.value }");

    // Generate enum class compareTo() method
    WriteLine();
    WriteLineIndent("override fun compareTo(other: " + enum_name + "): Int");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (value == null)");
    Indent(1);
    WriteLineIndent("return -1");
    Indent(-1);
    WriteLineIndent("if (other.value == null)");
    Indent(1);
    WriteLineIndent("return 1");
    Indent(-1);
    WriteLineIndent("return (value!!.raw - other.value!!.raw).toInt()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class equals() method
    WriteLine();
    WriteLineIndent("override fun equals(other: Any?): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + enum_name + "::class.java.isAssignableFrom(other.javaClass))");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val enm = other as " + enum_name + "? ?: return false");
    WriteLine();
    WriteLineIndent("if (enm.value == null)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("if (value!!.raw != enm.value!!.raw)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class hashCode() method
    WriteLine();
    WriteLineIndent("override fun hashCode(): Int");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var hash = 17");
    WriteLineIndent("hash = hash * 31 + if (value != null) value!!.hashCode() else 0");
    WriteLineIndent("return hash");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class toString() method
    WriteLine();
    WriteLineIndent("override fun toString(): String");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return if (value != null) value!!.toString() else \"<unknown>\"");
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate enum class footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorKotlin::GenerateEnumJson(const std::shared_ptr<Package>& p, const std::shared_ptr<EnumType>& e)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string enum_name = domain + package + "." + *e->name;
    std::string adapter_name = *e->name + "Json";

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the output file
    CppCommon::Path output = path / (adapter_name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string enum_type = (e->base && !e->base->empty()) ? *e->base : "int32";

    // Generate JSON adapter body
    WriteLine();
    WriteLineIndent("class " + adapter_name + " : com.google.gson.JsonSerializer<" + enum_name + ">, com.google.gson.JsonDeserializer<" + enum_name + ">");
    WriteLineIndent("{");
    Indent(1);

    // Generate JSON adapter serialize() method
    WriteLineIndent("override fun serialize(src: " + enum_name + ", typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return com.google.gson.JsonPrimitive(src.raw" + ConvertEnumFrom(enum_type) + ")");
    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter deserialize() method
    WriteLine();
    WriteLineIndent("@Throws(com.google.gson.JsonParseException::class)");
    WriteLineIndent("override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext): " + enum_name);
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return " + enum_name + "(json.asJsonPrimitive." + ConvertEnumGet(enum_type) + ")");
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorKotlin::GenerateFlags(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f, const CppCommon::Path& path)
{
    std::string flags_name = *f->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (flags_name + ".kt");
    WriteBegin();

    // Generate flags header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);
    std::string flags_mapping_type = ConvertEnumBase(flags_type);
    std::string flags_int = ConvertEnumFlags(flags_type);
    std::string flags_to = ConvertEnumTo(flags_type);

    // Generate flags body
    WriteLine();
    WriteLineIndent("@Suppress(\"EnumEntryName\", \"MemberVisibilityCanBePrivate\", \"RemoveRedundantCallsOfConversionMethods\")");
    WriteLineIndent("enum class " + flags_name);
    WriteLineIndent("{");
    Indent(1);
    if (f->body)
    {
        bool first = true;
        for (const auto& value : f->body->values)
        {
            WriteIndent(std::string(first ? "" : ", ") + *value->name + "(");
            if (value->value)
            {
                if (value->value->constant && !value->value->constant->empty())
                    Write(ConvertEnumConstant(flags_type, flags_type, *value->value->constant, true));
                else if (value->value->reference && !value->value->reference->empty())
                    Write(ConvertEnumConstant(flags_type, "", *value->value->reference, true));
            }
            WriteLine(")");
            first = false;
        }
        WriteLineIndent(";");
        WriteLine();
    }
    else
        WriteIndent("unknown(0);");

    // Generate flags class value
    WriteLineIndent("var raw: " + flags_mapping_type + " = " + ConvertEnumConstant(flags_type, flags_type, "0", false));
    Indent(1);
    WriteLineIndent("private set");
    Indent(-1);

    // Generate flags class constructors
    WriteLine();
    if ((flags_type == "char") || (flags_type == "wchar"))
        WriteLineIndent("constructor(value: Char) { this.raw = value" + flags_to + " }");
    if (IsUnsignedType(flags_type))
    {
        WriteLineIndent("constructor(value: UByte) { this.raw = value" + flags_to + " }");
        WriteLineIndent("constructor(value: UShort) { this.raw = value" + flags_to + " }");
        WriteLineIndent("constructor(value: UInt) { this.raw = value" + flags_to + " }");
        WriteLineIndent("constructor(value: ULong) { this.raw = value" + flags_to + " }");
    }
    else
    {
        WriteLineIndent("constructor(value: Byte) { this.raw = value" + flags_to + " }");
        WriteLineIndent("constructor(value: Short) { this.raw = value" + flags_to + " }");
        WriteLineIndent("constructor(value: Int) { this.raw = value" + flags_to + " }");
        WriteLineIndent("constructor(value: Long) { this.raw = value" + flags_to + " }");
    }
    WriteLineIndent("constructor(value: " + flags_name + ") { this.raw = value.raw }");

    // Generate flags hasFlags() methods
    WriteLine();
    WriteLineIndent("fun hasFlags(flags: " + flags_base_type + "): Boolean = ((raw" + flags_int + " and flags" + flags_int + ") != " + ConvertEnumConstant(flags_type, flags_type, "0", false) + ") && ((raw" + flags_int + " and flags" + flags_int + ") == flags" + flags_int + ")");
    WriteLineIndent("fun hasFlags(flags: " + flags_name + "): Boolean = hasFlags(flags.raw)");

    // Generate flags getAllSet(), getNoneSet(), getCurrentSet() methods
    WriteLine();
    WriteLineIndent("val allSet: java.util.EnumSet<" + flags_name + "> get() = java.util.EnumSet.allOf(" + flags_name + "::class.java)");
    WriteLineIndent("val noneSet: java.util.EnumSet<" + flags_name + "> get() = java.util.EnumSet.noneOf(" + flags_name + "::class.java)");
    WriteLineIndent("val currentSet: java.util.EnumSet<" + flags_name + "> get()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val result = java.util.EnumSet.noneOf(" + flags_name + "::class.java)");
    if (f->body)
    {
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if ((raw" + flags_int + " and " + *value->name + ".raw" + flags_int + ") != " + ConvertEnumConstant(flags_type, flags_type, "0", false) + ")");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("result.add(" + *value->name + ")");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return result");
    Indent(-1);
    WriteLineIndent("}");

    // Generate enum toString() method
    WriteLine();
    WriteLineIndent("override fun toString(): String");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val sb = StringBuilder()");
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("var first = true");
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if (hasFlags(" + *value->name + "))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("sb.append(if (first) \"\" else \"|\").append(\"" + *value->name + "\")");
            WriteLineIndent("@Suppress(\"UNUSED_VALUE\")");
            WriteLineIndent("first = false");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return sb.toString()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags mapping
    WriteLine();
    WriteLineIndent("companion object");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("private val mapping = java.util.HashMap<" + flags_mapping_type + ", " + flags_name + ">()");
    WriteLine();
    WriteLineIndent("init");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("for (value in " + flags_name + ".values())");
    Indent(1);
    WriteLineIndent("mapping[value.raw] = value");
    Indent(-1);
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags mapValue() method
    WriteLine();
    WriteLineIndent("fun mapValue(value: " + flags_mapping_type + "): " + flags_name + "?" +" { return mapping[value] }");

    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate flags footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);

    // Generate flags wrapper class
    GenerateFlagsClass(p, f, path);

    // Generate flags JSON adapter
    if (JSON())
        GenerateFlagsJson(p, f);

    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";

    // Generate flags field model
    GenerateFBEFieldModelEnumFlags(domain, *p->name, *f->name, flags_type);

    // Generate flags final model
    if (Final())
        GenerateFBEFinalModelEnumFlags(domain, *p->name, *f->name, flags_type);
}

void GeneratorKotlin::GenerateFlagsClass(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f, const CppCommon::Path& path)
{
    std::string flags_name = *f->name;
    std::string flags_type_name = *f->name + "Enum";

    // Generate the output file
    CppCommon::Path output = path / (flags_name + ".kt");
    WriteBegin();

    // Generate flags class header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";
    std::string flags_base_type = ConvertEnumType(flags_type);
    std::string flags_int = ConvertEnumFlags(flags_type);
    std::string flags_to = ConvertEnumTo(flags_type);

    // Generate flags class body
    WriteLine();
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"RemoveRedundantCallsOfConversionMethods\")");
    WriteLineIndent("class " + flags_name + " : Comparable<" + flags_name + ">");
    WriteLineIndent("{");
    Indent(1);
    if (f->body)
    {
        WriteLineIndent("companion object");
        WriteLineIndent("{");
        Indent(1);
        if (!f->body->values.empty())
        {
            for (const auto& value : f->body->values)
                WriteLineIndent("val " + *value->name + " = " + flags_name + "(" + flags_type_name + "." + *value->name + ")");
            WriteLine();
        }

        // Generate flags class fromSet() method
        if (f->body->values.empty())
            WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
        WriteLineIndent("fun fromSet(set: java.util.EnumSet<" + flags_type_name + ">): " + flags_name);
        WriteLineIndent("{");
        Indent(1);
        WriteLineIndent("@Suppress(\"CanBeVal\")");
        WriteLineIndent("var result = " + ConvertEnumConstant(flags_type, flags_type, "0", false));
        if (f->body)
        {
            for (const auto& value : f->body->values)
            {
                WriteLineIndent("if (set.contains(" + *value->name + ".value!!))");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("result = result" + flags_int + " or " + *value->name + ".raw" + flags_int);
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        WriteLineIndent("return " + flags_name + "(result" + flags_to + ")");
        Indent(-1);
        WriteLineIndent("}");

        Indent(-1);
        WriteLineIndent("}");
        WriteLine();
    }

    // Generate flags class value
    WriteLineIndent("var value: " + flags_type_name + "?" + " = " + flags_type_name + ".values()[0]");
    Indent(1);
    WriteLineIndent("private set");
    Indent(-1);
    WriteLine();

    // Generate flags raw value
    WriteLineIndent("var raw: " + flags_base_type + " = value!!.raw");
    Indent(1);
    WriteLineIndent("private set");
    Indent(-1);
    WriteLine();

    // Generate flags class constructors
    WriteLineIndent("constructor()");
    WriteLineIndent("constructor(value: " + flags_base_type + ") { setEnum(value) }");
    WriteLineIndent("constructor(value: " + flags_type_name + ") { setEnum(value) }");
    WriteLineIndent("constructor(value: java.util.EnumSet<" + flags_type_name + ">) { setEnum(value) }");
    WriteLineIndent("constructor(value: " + flags_name + ") { setEnum(value) }");
    WriteLine();

    // Generate flags class setDefault() method
    WriteLineIndent("fun setDefault() { setEnum(0" + flags_to + ") }");
    WriteLine();

    // Generate flags class setEnum() methods
    WriteLineIndent("fun setEnum(value: " + flags_base_type + ") { this.raw = value; this.value = " + flags_type_name + ".mapValue(value) }");
    WriteLineIndent("fun setEnum(value: " + flags_type_name + ") { this.value = value; this.raw = value.raw; }");
    WriteLineIndent("fun setEnum(value: java.util.EnumSet<" + flags_type_name + ">) { setEnum(" + flags_name + ".fromSet(value)) }");
    WriteLineIndent("fun setEnum(value: " + flags_name + ") { this.value = value.value; this.raw = value.raw }");

    // Generate flags class hasFlags() methods
    WriteLine();
    WriteLineIndent("fun hasFlags(flags: " + flags_base_type + "): Boolean = ((raw" + flags_int + " and flags" + flags_int + ") != " + ConvertEnumConstant(flags_type, flags_type, "0", false) + ") && ((raw" + flags_int + " and flags" + flags_int + ") == flags" + flags_int + ")");
    WriteLineIndent("fun hasFlags(flags: " + flags_type_name + "): Boolean = hasFlags(flags.raw)");
    WriteLineIndent("fun hasFlags(flags: " + flags_name + "): Boolean = hasFlags(flags.raw)");

    // Generate flags class setFlags() methods
    WriteLine();
    WriteLineIndent("fun setFlags(flags: " + flags_base_type + "): " + flags_name + " { setEnum((raw" + flags_int + " or flags" + flags_int + ")" + flags_to + "); return this }");
    WriteLineIndent("fun setFlags(flags: " + flags_type_name + "): " + flags_name + " { setFlags(flags.raw); return this }");
    WriteLineIndent("fun setFlags(flags: " + flags_name + "): " + flags_name + " { setFlags(flags.raw); return this }");

    // Generate flags class removeFlags() methods
    WriteLine();
    WriteLineIndent("fun removeFlags(flags: " + flags_base_type + "): " + flags_name + " { setEnum((raw" + flags_int + " and flags" + flags_int + ".inv())" + flags_to + "); return this }");
    WriteLineIndent("fun removeFlags(flags: " + flags_type_name + "): " + flags_name + " { removeFlags(flags.raw); return this }");
    WriteLineIndent("fun removeFlags(flags: " + flags_name + "): " + flags_name + " { removeFlags(flags.raw); return this }");

    // Generate flags class getAllSet(), getNoneSet() and getCurrentSet() methods
    WriteLine();
    WriteLineIndent("val allSet: java.util.EnumSet<" + flags_type_name + "> get() = value!!.allSet");
    WriteLineIndent("val noneSet: java.util.EnumSet<" + flags_type_name + "> get() = value!!.noneSet");
    WriteLineIndent("val currentSet: java.util.EnumSet<" + flags_type_name + "> get() = value!!.currentSet");

    // Generate flags class compareTo() method
    WriteLine();
    WriteLineIndent("override fun compareTo(other: " + flags_name + "): Int");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return (raw - other.raw).toInt()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class equals() method
    WriteLine();
    WriteLineIndent("override fun equals(other: Any?): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + flags_name + "::class.java.isAssignableFrom(other.javaClass))");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val flg = other as " + flags_name + "? ?: return false");
    WriteLine();
    WriteLineIndent("if (raw != flg.raw)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class hashCode() method
    WriteLine();
    WriteLineIndent("override fun hashCode(): Int");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var hash = " + ConvertEnumConstant(flags_type, flags_type, "17", false));
    WriteLineIndent("hash = hash * " + ConvertEnumConstant(flags_type, flags_type, "31", false) + " + raw" + flags_int);
    WriteLineIndent("return hash.toInt()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class toString() method
    WriteLine();
    WriteLineIndent("override fun toString(): String");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val sb = StringBuilder()");
    if (f->body && !f->body->values.empty())
    {
        WriteLineIndent("var first = true");
        for (const auto& value : f->body->values)
        {
            WriteLineIndent("if (hasFlags(" + *value->name + ".raw))");
            WriteLineIndent("{");
            Indent(1);
            WriteLineIndent("sb.append(if (first) \"\" else \"|\").append(\"" + *value->name + "\")");
            WriteLineIndent("@Suppress(\"UNUSED_VALUE\")");
            WriteLineIndent("first = false");
            Indent(-1);
            WriteLineIndent("}");
        }
    }
    WriteLineIndent("return sb.toString()");
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate flags class footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorKotlin::GenerateFlagsJson(const std::shared_ptr<Package>& p, const std::shared_ptr<FlagsType>& f)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string flags_name = domain + package + "." + *f->name;
    std::string adapter_name = *f->name + "Json";

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the output file
    CppCommon::Path output = path / (adapter_name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    std::string flags_type = (f->base && !f->base->empty()) ? *f->base : "int32";

    // Generate JSON adapter body
    WriteLine();
    WriteLineIndent("class " + adapter_name + " : com.google.gson.JsonSerializer<" + flags_name + ">, com.google.gson.JsonDeserializer<" + flags_name + ">");
    WriteLineIndent("{");
    Indent(1);

    // Generate JSON adapter serialize() method
    WriteLine();
    WriteLineIndent("@Override");
    WriteLineIndent("override fun serialize(src: " + flags_name + ", typeOfSrc: java.lang.reflect.Type, context: com.google.gson.JsonSerializationContext): com.google.gson.JsonElement");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return com.google.gson.JsonPrimitive(src.raw" + ConvertEnumFrom(flags_type) + ")");
    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter deserialize() method
    WriteLine();
    WriteLineIndent("@Throws(com.google.gson.JsonParseException::class)");
    WriteLineIndent("override fun deserialize(json: com.google.gson.JsonElement, type: java.lang.reflect.Type, context: com.google.gson.JsonDeserializationContext):" + flags_name);
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return " + flags_name + "(json.asJsonPrimitive." + ConvertEnumGet(flags_type) + ")");
    Indent(-1);
    WriteLineIndent("}");

    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON adapter footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);
}

void GeneratorKotlin::GenerateStruct(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s, const CppCommon::Path& path)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    bool first;

    // Generate the output file
    CppCommon::Path output = path / (*s->name + ".kt");
    WriteBegin();

    // Generate struct header
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(p);

    // Generate struct begin
    WriteLine();
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"RemoveRedundantCallsOfConversionMethods\")");
    WriteIndent("open class " + *s->name);
    if (s->base && !s->base->empty())
        Write(" : " + ConvertTypeName(domain, "", *s->base, false));
    else
        Write(" : Comparable<Any?>");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate struct body
    if (s->body && !s->body->fields.empty())
    {
        for (const auto& field : s->body->fields)
            WriteLineIndent("var " + *field->name + ": " + ConvertTypeName(domain, "", *field, false));
        WriteLine();
    }

    // Generate struct FBE type property
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("@Transient override var fbeType: Long = " + ConvertTypeName(domain, "", *s->base, false) + ".fbeTypeConst");
    else if (s->base && !s->base->empty())
        WriteLineIndent("@Transient override var fbeType: Long = " + std::to_string(s->type));
    else
        WriteLineIndent("@Transient open var fbeType: Long = " + std::to_string(s->type));

    // Generate struct default constructor
    if ((!s->base || s->base->empty()) && (!s->body || s->body->fields.empty()))
    {
        WriteLine();
        WriteLineIndent("constructor()");
    }

    // Generate struct initialization constructor
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        first = true;
        WriteLine();
        WriteIndent("constructor(");
        if (s->base && !s->base->empty())
        {
            Write("parent: " + ConvertTypeName(domain, "", *s->base, false) + " = " + ConvertTypeName(domain, "", *s->base, false) + "()");
            first = false;
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                Write(std::string(first ? "" : ", ") + *field->name + ": " + ConvertTypeName(domain, "", *field, false) + " = " + ConvertDefault(domain, "", *field));
                first = false;
            }
        }
        Write(")");
        if (s->base && !s->base->empty())
            WriteLine(": super(parent)");
        else
            WriteLine();
        WriteLineIndent("{");
        Indent(1);
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent("this." + *field->name + " = " + *field->name);
        Indent(-1);
        WriteLineIndent("}");
    }

    // Generate struct copy constructor
    WriteLine();
    WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
    WriteIndent("constructor(other: " + *s->name + ")");
    if (s->base && !s->base->empty())
        WriteLine(": super(other)");
    else
        WriteLine();
    WriteLineIndent("{");
    Indent(1);
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("this." + *field->name + " = other." + *field->name);
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct clone() method
    WriteLine();
    if (s->base && !s->base->empty())
        WriteLineIndent("override fun clone(): " + *s->name);
    else
        WriteLineIndent("open fun clone(): " + *s->name);
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("// Serialize the struct to the FBE stream");
    WriteLineIndent("val writer = " + domain + *p->name + ".fbe." + *s->name + "Model()");
    WriteLineIndent("writer.serialize(this)");
    WriteLine();
    WriteLineIndent("// Deserialize the struct from the FBE stream");
    WriteLineIndent("val reader = " + domain + *p->name + ".fbe." + *s->name + "Model()");
    WriteLineIndent("reader.attach(writer.buffer)");
    WriteLineIndent("return reader.deserialize()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct compareTo() method
    WriteLine();
    WriteLineIndent("override fun compareTo(other: Any?): Int");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return -1");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + *s->name + "::class.java.isAssignableFrom(other.javaClass))");
    Indent(1);
    WriteLineIndent("return -1");
    Indent(-1);
    WriteLine();
    WriteLineIndent("@Suppress(\"UNUSED_VARIABLE\")");
    WriteLineIndent("val obj = other as " + *s->name + "? ?: return -1");
    WriteLine();
    WriteLineIndent("@Suppress(\"VARIABLE_WITH_REDUNDANT_INITIALIZER\", \"CanBeVal\", \"UnnecessaryVariable\")");
    WriteLineIndent("var result = 0");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("result = super.compareTo(obj)");
        WriteLineIndent("if (result != 0)");
        Indent(1);
        WriteLineIndent("return result");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                WriteLineIndent("result = " + *field->name + ".compareTo(obj." + *field->name + ")");
                WriteLineIndent("if (result != 0)");
                Indent(1);
                WriteLineIndent("return result");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return result");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct equals() method
    WriteLine();
    WriteLineIndent("override fun equals(other: Any?): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if (other == null)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("if (!" + *s->name + "::class.java.isAssignableFrom(other.javaClass))");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("@Suppress(\"UNUSED_VARIABLE\")");
    WriteLineIndent("val obj = other as " + *s->name + "? ?: return false");
    WriteLine();
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("if (!super.equals(obj))");
        Indent(1);
        WriteLineIndent("return false");
        Indent(-1);
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->keys)
            {
                if (IsPrimitiveType(*field->type, field->optional))
                    WriteLineIndent("if (" + *field->name + " != obj." + *field->name + ")");
                else
                    WriteLineIndent("if (!" + *field->name + ".equals(obj." + *field->name + "))");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
            }
        }
    }
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct hashCode() method
    WriteLine();
    WriteLineIndent("override fun hashCode(): Int");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("@Suppress(\"CanBeVal\", \"UnnecessaryVariable\")");
    WriteLineIndent("var hash = 17");
    if (s->base && !s->base->empty())
        WriteLineIndent("hash = hash * 31 + super.hashCode()");
    if (s->body)
    {
        for (const auto& field : s->body->fields)
            if (field->keys)
                WriteLineIndent("hash = hash * 31 + " + *field->name + ".hashCode()");
    }
    WriteLineIndent("return hash");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct toString() method
    WriteLine();
    WriteLineIndent("override fun toString(): String");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val sb = StringBuilder()");
    WriteLineIndent("sb.append(\"" + *s->name + "(\")");
    first = true;
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("sb.append(super.toString())");
        first = false;
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            if (field->attributes && field->attributes->hidden)
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=***\");");
            else if (field->array || field->vector)
            {
                WriteLineIndent("@Suppress(\"ConstantConditionIf\")");
                WriteLineIndent("if (true)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("var first = true");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size" + ").append(\"][\")");
                WriteLineIndent("for (item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true, false));
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"]\")");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->list)
            {
                WriteLineIndent("@Suppress(\"ConstantConditionIf\")");
                WriteLineIndent("if (true)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("var first = true");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size" + ").append(\"]<\")");
                WriteLineIndent("for (item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true, false));
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\">\")");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->set)
            {
                WriteLineIndent("@Suppress(\"ConstantConditionIf\")");
                WriteLineIndent("if (true)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("var first = true");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size" + ").append(\"]{\")");
                WriteLineIndent("for (item in " + *field->name + ")");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item", field->optional, true, false));
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"}\")");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->map)
            {
                WriteLineIndent("@Suppress(\"ConstantConditionIf\")");
                WriteLineIndent("if (true)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("var first = true");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size" + ").append(\"]<{\")");
                WriteLineIndent("for (item in " + *field->name + ".entries)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.key", false, true, false));
                WriteLineIndent("sb.append(\"->\")");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.value", field->optional, false, true));
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"}>\")");
                Indent(-1);
                WriteLineIndent("}");
            }
            else if (field->hash)
            {
                WriteLineIndent("@Suppress(\"ConstantConditionIf\")");
                WriteLineIndent("if (true)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("var first = true");
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=[\").append(" + *field->name + ".size" + ").append(\"][{\")");
                WriteLineIndent("for (item in " + *field->name + ".entries)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent(ConvertOutputStreamValue(*field->key, "item.key", false, true, false));
                WriteLineIndent("sb.append(\"->\")");
                WriteLineIndent(ConvertOutputStreamValue(*field->type, "item.value", field->optional, false, true));
                WriteLineIndent("first = false");
                Indent(-1);
                WriteLineIndent("}");
                WriteLineIndent("sb.append(\"}]\")");
                Indent(-1);
                WriteLineIndent("}");
            }
            else
                WriteLineIndent("sb.append(\"" + std::string(first ? "" : ",") + *field->name + "=\"); " + ConvertOutputStreamValue(*field->type, *field->name, field->optional, false, true));
            first = false;
        }
    }
    WriteLineIndent("sb.append(\")\")");
    WriteLineIndent("return sb.toString()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct JSON methods
    if (JSON())
    {
        WriteLine();
        WriteLineIndent(std::string((s->base && !s->base->empty()) ? "override" : "open") + " fun toJson(): String = " + domain + *p->name + ".fbe.Json.engine.toJson(this)");
    }

    // Generate struct companion object
    WriteLine();
    WriteLineIndent("companion object");
    WriteLineIndent("{");
    Indent(1);
    if (!s->base || s->base->empty())
        WriteLineIndent("const val fbeTypeConst: Long = " + std::to_string(s->type));
    if (JSON())
        WriteLineIndent("fun fromJson(json: String): " + *s->name + " = " + domain + *p->name + ".fbe.Json.engine.fromJson(json, " + *s->name + "::class.java)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct end
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct footer
    GenerateFooter();

    // Store the output file
    WriteEnd();
    Store(output);

    // Generate struct field models
    GenerateStructFieldModel(p, s);
    GenerateStructModel(p, s);

    // Generate struct final models
    if (Final())
    {
        GenerateStructFinalModel(p, s);
        GenerateStructModelFinal(p, s);
    }
}

void GeneratorKotlin::GenerateStructFieldModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + package + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FieldModel" + *s->name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct field model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " field model");
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"RemoveRedundantCallsOfConversionMethods\", \"ReplaceGetOrSet\")");
    WriteLineIndent("class FieldModel" + *s->name + "(buffer: " + domain + "fbe.Buffer, offset: Long) : " + domain + "fbe.FieldModel(buffer, offset)");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct field model accessors
    std::string prev_offset("4");
    std::string prev_size("4");
    if (s->base && !s->base->empty())
    {
        WriteLineIndent("val parent: " + ConvertBaseFieldName(domain, *s->base, false) + " = " + ConvertBaseFieldName(domain, *s->base, false) + "(buffer, " + prev_offset + " + " + prev_size + ")");
        prev_offset = "parent.fbeOffset";
        prev_size = "parent.fbeBody - 4 - 4";
    }
    if (s->body)
    {
        for (const auto& field : s->body->fields)
        {
            WriteLineIndent("val " + *field->name + ": " + ConvertTypeFieldDeclaration(domain, *field, false) + " = " + ConvertTypeFieldInitialization(domain, *field, prev_offset + " + " + prev_size, false));
            prev_offset = *field->name + ".fbeOffset";
            prev_size = *field->name + ".fbeSize";
        }
    }

    // Generate struct field model FBE properties
    WriteLine();
    WriteLineIndent("// Field size");
    WriteLineIndent("override val fbeSize: Long = 4");
    WriteLine();
    WriteLineIndent("// Field body size");
    WriteLineIndent("val fbeBody: Long = (4 + 4");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbeBody - 4 - 4");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbeSize");
    WriteLineIndent(")");
    Indent(-1);
    WriteLine();
    WriteLineIndent("// Field extra size");
    WriteLineIndent("override val fbeExtra: Long get()");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructOffset = readUInt32(fbeOffset).toLong()");
    WriteLineIndent("if ((fbeStructOffset == 0L) || ((_buffer.offset + fbeStructOffset + 4) > _buffer.size))");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset)");
    WriteLine();
    WriteLineIndent("val fbeResult = (fbeBody");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbeExtra");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbeExtra");
    WriteLineIndent(")");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.unshift(fbeStructOffset)");
    WriteLine();
    WriteLineIndent("return fbeResult");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("// Field type");
    WriteLineIndent("var fbeType: Long = fbeTypeConst");
    WriteLine();
    WriteLineIndent("companion object");
    WriteLineIndent("{");
    Indent(1);
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("const val fbeTypeConst: Long = " + ConvertBaseFieldName(domain, *s->base, false) + ".fbeTypeConst");
    else
        WriteLineIndent("const val fbeTypeConst: Long = " + std::to_string(s->type));
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model verify() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("fun verify(fbeVerifyType: Boolean = true): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)");
    Indent(1);
    WriteLineIndent("return true");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructOffset = readUInt32(fbeOffset).toLong()");
    WriteLineIndent("if ((fbeStructOffset == 0L) || ((_buffer.offset + fbeStructOffset + 4 + 4) > _buffer.size))");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructSize = readUInt32(fbeStructOffset).toLong()");
    WriteLineIndent("if (fbeStructSize < (4 + 4))");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructType = readUInt32(fbeStructOffset + 4).toLong()");
    WriteLineIndent("if (fbeVerifyType && (fbeStructType != fbeType))");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset)");
    WriteLineIndent("val fbeResult = verifyFields(fbeStructSize)");
    WriteLineIndent("_buffer.unshift(fbeStructOffset)");
    WriteLineIndent("return fbeResult");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model verifyFields() method
    WriteLine();
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
    WriteLineIndent("fun verifyFields(fbeStructSize: Long): Boolean");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("var fbeCurrentSize = 4L + 4L");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + parent.fbeBody - 4 - 4) > fbeStructSize)");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
            WriteLineIndent("if (!parent.verifyFields(fbeStructSize))");
            Indent(1);
            WriteLineIndent("return false");
            Indent(-1);
            WriteLineIndent("fbeCurrentSize += parent.fbeBody - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".fbeSize) > fbeStructSize)");
                Indent(1);
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("if (!" + *field->name + ".verify())");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("fbeCurrentSize += " + *field->name + ".fbeSize");
            }
        }
        WriteLine();
    }
    WriteLineIndent("return true");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getBegin() method
    WriteLine();
    WriteLineIndent("// Get the struct value (begin phase)");
    WriteLineIndent("fun getBegin(): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructOffset = readUInt32(fbeOffset).toLong()");
    WriteLineIndent("assert((fbeStructOffset > 0) && ((_buffer.offset + fbeStructOffset + 4 + 4) <= _buffer.size)) { \"Model is broken!\" }");
    WriteLineIndent("if ((fbeStructOffset == 0L) || ((_buffer.offset + fbeStructOffset + 4 + 4) > _buffer.size))");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructSize = readUInt32(fbeStructOffset).toLong()");
    WriteLineIndent("assert(fbeStructSize >= (4 + 4)) { \"Model is broken!\" }");
    WriteLineIndent("if (fbeStructSize < (4 + 4))");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset)");
    WriteLineIndent("return fbeStructOffset");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getEnd() method
    WriteLine();
    WriteLineIndent("// Get the struct value (end phase)");
    WriteLineIndent("fun getEnd(fbeBegin: Long)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.unshift(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model get() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("fun get(fbeValue: " + struct_name + " = " + struct_name + "()): " + struct_name);
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val fbeBegin = getBegin()");
    WriteLineIndent("if (fbeBegin == 0L)");
    Indent(1);
    WriteLineIndent("return fbeValue");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructSize = readUInt32(0).toLong()");
    WriteLineIndent("getFields(fbeValue, fbeStructSize)");
    WriteLineIndent("getEnd(fbeBegin)");
    WriteLineIndent("return fbeValue");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model getFields() method
    WriteLine();
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
    WriteLineIndent("fun getFields(fbeValue: " + struct_name + ", fbeStructSize: Long)");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("var fbeCurrentSize = 4L + 4L");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("if ((fbeCurrentSize + parent.fbeBody - 4 - 4) <= fbeStructSize)");
            Indent(1);
            WriteLineIndent("parent.getFields(fbeValue, fbeStructSize)");
            Indent(-1);
            WriteLineIndent("fbeCurrentSize += parent.fbeBody - 4 - 4");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent("if ((fbeCurrentSize + " + *field->name + ".fbeSize) <= fbeStructSize)");
                Indent(1);
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent(*field->name + ".get(fbeValue." + *field->name + ")");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + *field->name + ".get(" + (field->value ? ConvertConstant(domain, package, *field->type, *field->value, field->optional) : "") + ")");
                Indent(-1);
                WriteLineIndent("else");
                Indent(1);
                if (field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbeValue." + *field->name + ".clear()");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + ConvertDefault(domain, package, *field));
                Indent(-1);
                WriteLineIndent("fbeCurrentSize += " + *field->name + ".fbeSize");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setBegin() method
    WriteLine();
    WriteLineIndent("// Set the struct value (begin phase)");
    WriteLineIndent("fun setBegin(): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("assert((_buffer.offset + fbeOffset + fbeSize) <= _buffer.size) { \"Model is broken!\" }");
    WriteLineIndent("if ((_buffer.offset + fbeOffset + fbeSize) > _buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructSize = fbeBody");
    WriteLineIndent("val fbeStructOffset = _buffer.allocate(fbeStructSize) - _buffer.offset");
    WriteLineIndent("assert((fbeStructOffset > 0) && ((_buffer.offset + fbeStructOffset + fbeStructSize) <= _buffer.size)) { \"Model is broken!\" }");
    WriteLineIndent("if ((fbeStructOffset <= 0) || ((_buffer.offset + fbeStructOffset + fbeStructSize) > _buffer.size))");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("write(fbeOffset, fbeStructOffset.toUInt())");
    WriteLineIndent("write(fbeStructOffset, fbeStructSize.toUInt())");
    WriteLineIndent("write(fbeStructOffset + 4, fbeType.toUInt())");
    WriteLine();
    WriteLineIndent("_buffer.shift(fbeStructOffset)");
    WriteLineIndent("return fbeStructOffset");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setEnd() method
    WriteLine();
    WriteLineIndent("// Set the struct value (end phase)");
    WriteLineIndent("fun setEnd(fbeBegin: Long)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.unshift(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model set() method
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("fun set(fbeValue: " + struct_name + ")");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val fbeBegin = setBegin()");
    WriteLineIndent("if (fbeBegin == 0L)");
    Indent(1);
    WriteLineIndent("return");
    Indent(-1);
    WriteLine();
    WriteLineIndent("setFields(fbeValue)");
    WriteLineIndent("setEnd(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model setFields() method
    WriteLine();
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
    WriteLineIndent("fun setFields(fbeValue: " + struct_name + ")");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        if (s->base && !s->base->empty())
            WriteLineIndent("parent.setFields(fbeValue)");
        if (s->body)
            for (const auto& field : s->body->fields)
                WriteLineIndent(*field->name + ".set(fbeValue." + *field->name + ")");
    }
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct field model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateStructModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + package + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / (*s->name + "Model.kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " model");
    WriteLineIndent("class " + *s->name + "Model : " + domain + "fbe.Model");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct model accessor
    WriteLineIndent("val model: FieldModel" + *s->name);

    // Generate struct model constructors
    WriteLine();
    WriteLineIndent("constructor() { model = FieldModel" + *s->name + "(buffer, 4) }");
    WriteLineIndent("constructor(buffer: " + domain + "fbe.Buffer) : super(buffer) { model = FieldModel" + *s->name + "(buffer, 4) }");

    // Generate struct model FBE properties
    WriteLine();
    WriteLineIndent("// Model size");
    WriteLineIndent("fun fbeSize(): Long = model.fbeSize + model.fbeExtra");
    WriteLineIndent("// Model type");
    WriteLineIndent("var fbeType: Long = fbeTypeConst");
    WriteLine();
    WriteLineIndent("companion object");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("const val fbeTypeConst: Long = FieldModel" + *s->name + ".fbeTypeConst");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("fun verify(): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((buffer.offset + model.fbeOffset - 4) > buffer.size)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeFullSize = readUInt32(model.fbeOffset - 4).toLong()");
    WriteLineIndent("if (fbeFullSize < model.fbeSize)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return model.verify()");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model createBegin() method
    WriteLine();
    WriteLineIndent("// Create a new model (begin phase)");
    WriteLineIndent("fun createBegin(): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return buffer.allocate(4 + model.fbeSize)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model createEnd() method
    WriteLine();
    WriteLineIndent("// Create a new model (end phase)");
    WriteLineIndent("fun createEnd(fbeBegin: Long): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val fbeEnd = buffer.size");
    WriteLineIndent("val fbeFullSize = fbeEnd - fbeBegin");
    WriteLineIndent("write(model.fbeOffset - 4, fbeFullSize.toUInt())");
    WriteLineIndent("return fbeFullSize");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model serialize() method
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("fun serialize(value: " + struct_name + "): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val fbeBegin = createBegin()");
    WriteLineIndent("model.set(value)");
    WriteLineIndent("return createEnd(fbeBegin)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model deserialize() methods
    WriteLine();
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("fun deserialize(): " + struct_name + " { val value = " + struct_name + "(); deserialize(value); return value }");
    WriteLineIndent("@Suppress(\"UNUSED_VALUE\")");
    WriteLineIndent("fun deserialize(value: " + struct_name + "): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var valueRef = value");
    WriteLine();
    WriteLineIndent("if ((buffer.offset + model.fbeOffset - 4) > buffer.size)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("valueRef = " + struct_name + "()");
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("val fbeFullSize = readUInt32(model.fbeOffset - 4).toLong()");
    WriteLineIndent("assert(fbeFullSize >= model.fbeSize) { \"Model is broken!\" }");
    WriteLineIndent("if (fbeFullSize < model.fbeSize)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("valueRef = " + struct_name + "()");
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("valueRef = model.get(valueRef)");
    WriteLineIndent("return fbeFullSize");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("fun next(prev: Long)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("model.fbeShift(prev)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateStructFinalModel(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + package + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("FinalModel" + *s->name + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct final model begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"RemoveRedundantCallsOfConversionMethods\", \"ReplaceGetOrSet\")");
    WriteLineIndent("class FinalModel" + *s->name + "(buffer: " + domain + "fbe.Buffer, offset: Long) : " + domain + "fbe.FinalModel(buffer, offset)");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct final model accessors
    if (s->base && !s->base->empty())
        WriteLineIndent("val parent: " + ConvertBaseFieldName(domain, *s->base, true) + " = " + ConvertBaseFieldName(domain, *s->base, true) + "(buffer, 0)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("val " + *field->name + ": " + ConvertTypeFieldDeclaration(domain, *field, true) + " = " + ConvertTypeFieldInitialization(domain, *field, "0", true));

    // Generate struct final model FBE properties
    WriteLine();
    WriteLineIndent("// Get the allocation size");
    WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
    WriteLineIndent("fun fbeAllocationSize(fbeValue: " + struct_name + "): Long = (0");
    Indent(1);
    if (s->base && !s->base->empty())
        WriteLineIndent("+ parent.fbeAllocationSize(fbeValue)");
    if (s->body)
        for (const auto& field : s->body->fields)
            WriteLineIndent("+ " + *field->name + ".fbeAllocationSize(fbeValue." + *field->name + ")");
    WriteLineIndent(")");
    Indent(-1);
    WriteLine();
    WriteLineIndent("// Field type");
    WriteLineIndent("var fbeType: Long = fbeTypeConst");
    WriteLine();
    WriteLineIndent("companion object");
    WriteLineIndent("{");
    Indent(1);
    if (s->base && !s->base->empty() && (s->type == 0))
        WriteLineIndent("const val fbeTypeConst: Long = " + ConvertBaseFieldName(domain, *s->base, true) + ".fbeTypeConst");
    else
        WriteLineIndent("const val fbeTypeConst: Long = " + std::to_string(s->type));
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model verify() methods
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("override fun verify(): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbeOffset)");
    WriteLineIndent("val fbeResult = verifyFields()");
    WriteLineIndent("_buffer.unshift(fbeOffset)");
    WriteLineIndent("return fbeResult");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model verifyFields() method
    WriteLine();
    WriteLineIndent("// Check if the struct fields are valid");
    WriteLineIndent("fun verifyFields(): Long");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("var fbeCurrentOffset = 0L");
        WriteLineIndent("@Suppress(\"VARIABLE_WITH_REDUNDANT_INITIALIZER\")");
        WriteLineIndent("var fbeFieldSize = 0L");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbeOffset = fbeCurrentOffset");
            WriteLineIndent("fbeFieldSize = parent.verifyFields()");
            WriteLineIndent("if (fbeFieldSize == Long.MAX_VALUE)");
            Indent(1);
            WriteLineIndent("return Long.MAX_VALUE");
            Indent(-1);
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbeOffset = fbeCurrentOffset");
                WriteLineIndent("fbeFieldSize = " + *field->name + ".verify()");
                WriteLineIndent("if (fbeFieldSize == Long.MAX_VALUE)");
                Indent(1);
                WriteLineIndent("return Long.MAX_VALUE");
                Indent(-1);
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentOffset");
    }
    else
        WriteLineIndent("return 0L");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model get() methods
    WriteLine();
    WriteLineIndent("// Get the struct value");
    WriteLineIndent("fun get(fbeSize: " + domain + "fbe.Size, fbeValue: " + struct_name + " = " + struct_name + "()): " + struct_name);
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbeOffset)");
    WriteLineIndent("fbeSize.value = getFields(fbeValue)");
    WriteLineIndent("_buffer.unshift(fbeOffset)");
    WriteLineIndent("return fbeValue");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model getFields() method
    WriteLine();
    WriteLineIndent("// Get the struct fields values");
    WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
    WriteLineIndent("fun getFields(fbeValue: " + struct_name + "): Long");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("var fbeCurrentOffset = 0L");
        WriteLineIndent("var fbeCurrentSize = 0L");
        WriteLineIndent("val fbeFieldSize = " + domain + "fbe.Size()");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbeOffset = fbeCurrentOffset");
            WriteLineIndent("fbeFieldSize.value = parent.getFields(fbeValue)");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value");
            WriteLineIndent("fbeCurrentSize += fbeFieldSize.value");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbeOffset = fbeCurrentOffset");
                if (field->array || field->vector || field->list || field->set || field->map || field->hash)
                    WriteLineIndent("fbeFieldSize.value = " + *field->name + ".get(fbeValue." + *field->name + ")");
                else
                    WriteLineIndent("fbeValue." + *field->name + " = " + *field->name + ".get(fbeFieldSize)");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value");
                WriteLineIndent("fbeCurrentSize += fbeFieldSize.value");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize");
    }
    else
        WriteLineIndent("return 0L");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model set() method
    WriteLine();
    WriteLineIndent("// Set the struct value");
    WriteLineIndent("fun set(fbeValue: " + struct_name + "): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_buffer.shift(fbeOffset)");
    WriteLineIndent("val fbeSize = setFields(fbeValue)");
    WriteLineIndent("_buffer.unshift(fbeOffset)");
    WriteLineIndent("return fbeSize");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model setFields() method
    WriteLine();
    WriteLineIndent("// Set the struct fields values");
    WriteLineIndent("@Suppress(\"UNUSED_PARAMETER\")");
    WriteLineIndent("fun setFields(fbeValue: " + struct_name + "): Long");
    WriteLineIndent("{");
    Indent(1);
    if ((s->base && !s->base->empty()) || (s->body && !s->body->fields.empty()))
    {
        WriteLineIndent("var fbeCurrentOffset = 0L");
        WriteLineIndent("var fbeCurrentSize = 0L");
        WriteLineIndent("val fbeFieldSize = " + domain + "fbe.Size()");
        if (s->base && !s->base->empty())
        {
            WriteLine();
            WriteLineIndent("parent.fbeOffset = fbeCurrentOffset");
            WriteLineIndent("fbeFieldSize.value = parent.setFields(fbeValue)");
            WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value");
            WriteLineIndent("fbeCurrentSize += fbeFieldSize.value");
        }
        if (s->body)
        {
            for (const auto& field : s->body->fields)
            {
                WriteLine();
                WriteLineIndent(*field->name + ".fbeOffset = fbeCurrentOffset");
                WriteLineIndent("fbeFieldSize.value = " + *field->name + ".set(fbeValue." + *field->name + ")");
                WriteLineIndent("fbeCurrentOffset += fbeFieldSize.value");
                WriteLineIndent("fbeCurrentSize += fbeFieldSize.value");
            }
        }
        WriteLine();
        WriteLineIndent("return fbeCurrentSize");
    }
    else
        WriteLineIndent("return 0L");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct final model end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateStructModelFinal(const std::shared_ptr<Package>& p, const std::shared_ptr<StructType>& s)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;
    std::string struct_name = domain + package + "." + *s->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / (*s->name + "FinalModel.kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate struct model final begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *s->name + " final model");
    WriteLineIndent("class " + *s->name + "FinalModel : " + domain + "fbe.Model");
    WriteLineIndent("{");
    Indent(1);

    // Generate struct model final accessor
    WriteLineIndent("private val _model: FinalModel" + *s->name);

    // Generate struct model final constructors
    WriteLine();
    WriteLineIndent("constructor() { _model = FinalModel" + *s->name + "(buffer, 8) }");
    WriteLineIndent("constructor(buffer: " + domain + "fbe.Buffer) : super(buffer) { _model = FinalModel" + *s->name + "(buffer, 8) }");

    // Generate struct model final FBE properties
    WriteLine();
    WriteLineIndent("// Model type");
    WriteLineIndent("var fbeType: Long = fbeTypeConst");
    WriteLine();
    WriteLineIndent("companion object");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("const val fbeTypeConst: Long = FinalModel" + *s->name + ".fbeTypeConst");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final verify() method
    WriteLine();
    WriteLineIndent("// Check if the struct value is valid");
    WriteLineIndent("fun verify(): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("if ((buffer.offset + _model.fbeOffset) > buffer.size)");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructSize = readUInt32(_model.fbeOffset - 8).toLong()");
    WriteLineIndent("val fbeStructType = readUInt32(_model.fbeOffset - 4).toLong()");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != fbeType))");
    Indent(1);
    WriteLineIndent("return false");
    Indent(-1);
    WriteLine();
    WriteLineIndent("return ((8 + _model.verify()) == fbeStructSize)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final serialize() method
    WriteLine();
    WriteLineIndent("// Serialize the struct value");
    WriteLineIndent("fun serialize(value: " + struct_name + "): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("val fbeInitialSize = buffer.size");
    WriteLine();
    WriteLineIndent("val fbeStructType = fbeType");
    WriteLineIndent("var fbeStructSize = 8 + _model.fbeAllocationSize(value)");
    WriteLineIndent("val fbeStructOffset = buffer.allocate(fbeStructSize) - buffer.offset");
    WriteLineIndent("assert((buffer.offset + fbeStructOffset + fbeStructSize) <= buffer.size) { \"Model is broken!\" }");
    WriteLineIndent("if ((buffer.offset + fbeStructOffset + fbeStructSize) > buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("fbeStructSize = 8 + _model.set(value)");
    WriteLineIndent("buffer.resize(fbeInitialSize + fbeStructSize)");
    WriteLine();
    WriteLineIndent("write(_model.fbeOffset - 8, fbeStructSize.toUInt())");
    WriteLineIndent("write(_model.fbeOffset - 4, fbeStructType.toUInt())");
    WriteLine();
    WriteLineIndent("return fbeStructSize");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final deserialize() methods
    WriteLine();
    WriteLineIndent("// Deserialize the struct value");
    WriteLineIndent("fun deserialize(): " + struct_name + " { val value = " + struct_name + "(); deserialize(value); return value }");
    WriteLineIndent("@Suppress(\"UNUSED_VALUE\")");
    WriteLineIndent("fun deserialize(value: " + struct_name + "): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("var valueRef = value");
    WriteLine();
    WriteLineIndent("assert((buffer.offset + _model.fbeOffset) <= buffer.size) { \"Model is broken!\" }");
    WriteLineIndent("if ((buffer.offset + _model.fbeOffset) > buffer.size)");
    Indent(1);
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeStructSize = readUInt32(_model.fbeOffset - 8).toLong()");
    WriteLineIndent("val fbeStructType = readUInt32(_model.fbeOffset - 4).toLong()");
    WriteLineIndent("assert((fbeStructSize > 0) && (fbeStructType == fbeType)) { \"Model is broken!\" }");
    WriteLineIndent("if ((fbeStructSize <= 0) || (fbeStructType != fbeType))");
    Indent(1);
    WriteLineIndent("return 8");
    Indent(-1);
    WriteLine();
    WriteLineIndent("val fbeSize = " + domain + "fbe.Size()");
    WriteLineIndent("valueRef = _model.get(fbeSize, valueRef)");
    WriteLineIndent("return 8 + fbeSize.value");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final next() method
    WriteLine();
    WriteLineIndent("// Move to the next struct value");
    WriteLineIndent("fun next(prev: Long)");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("_model.fbeShift(prev)");
    Indent(-1);
    WriteLineIndent("}");

    // Generate struct model final end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateProtocolVersion(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / ("ProtocolVersion.kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate protocol version class
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " protocol version");
    WriteLineIndent("object ProtocolVersion");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("// Protocol major version");
    WriteLineIndent("const val Major = " + std::to_string(p->version->major));
    WriteLineIndent("// Protocol minor version");
    WriteLineIndent("const val Minor = " + std::to_string(p->version->minor));
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateSender(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalSenderListener" : "ISenderListener");
    std::string sender = (final ? "FinalSender" : "Sender");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (sender + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate sender begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final sender");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " sender");
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"PropertyName\")");
    WriteLineIndent("open class " + sender + " : " + domain + "fbe.Sender, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported senders accessors
    if (p->import)
    {
        WriteLineIndent("// Imported senders");
        for (const auto& import : p->import->imports)
            WriteLineIndent("val " + *import + "Sender: " + domain + *import + ".fbe." + sender);
        WriteLine();
    }

    // Generate sender models accessors
    if (p->body)
    {
        WriteLineIndent("// Sender models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("val " + *s->name + "Model: " + *s->name + model);
        WriteLine();
    }

    // Generate sender constructors
    WriteLineIndent("constructor() : super(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Sender = " + domain + *import + ".fbe." + sender + "(buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = " + *s->name + model + "(buffer)");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("constructor(buffer: " + domain + "fbe.Buffer) : super(buffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Sender = " + domain + *import + ".fbe." + sender + "(buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = " + *s->name + model + "(buffer)");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate generic sender method
    WriteLineIndent("fun send(obj: Any): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return sendListener(this, obj)");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("@Suppress(\"JoinDeclarationAndAssignment\", \"UNUSED_PARAMETER\")");
    WriteLineIndent("fun sendListener(listener: " + listener + ", obj: Any): Long");
    WriteLineIndent("{");
    Indent(1);
    if (p->body && messages)
    {
        WriteLineIndent("when (obj)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("is " + struct_name + " -> if (obj.fbeType == " + *s->name + "Model.fbeType) return sendListener(listener, obj)");
            }
        }
        Indent(-1);
        WriteLineIndent("}");
    }
    WriteLine();
    if (p->import)
    {
        WriteLineIndent("// Try to send using imported senders");
        WriteLineIndent("@Suppress(\"CanBeVal\")");
        WriteLineIndent("var result: Long");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = " + *import + "Sender.sendListener(listener, obj)");
            WriteLineIndent("if (result > 0)");
            Indent(1);
            WriteLineIndent("return result");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate sender methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("fun send(value: " + struct_name + "): Long");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("return sendListener(this, value)");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("fun sendListener(listener: " + listener + ", value: " + struct_name + "): Long");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("val serialized = " + *s->name + "Model.serialize(value)");
                WriteLineIndent("assert(serialized > 0) { \"" + struct_name + " serialization failed!\" }");
                WriteLineIndent("assert(" + *s->name + "Model.verify()) { \"" + struct_name + " validation failed!\" }");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("val message = value.toString()");
                WriteLineIndent("listener.onSendLog(message)");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return sendSerialized(listener, serialized)");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    // Generate sender end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateSenderListener(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalSenderListener" : "ISenderListener");

    // Generate the file
    CppCommon::Path file = path / (listener + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate sender listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final sender listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " sender listener interface");
    WriteIndent("interface " + listener);
    if (p->import)
    {
        bool first = true;
        Write(" : ");
        for (const auto& import : p->import->imports)
        {
            Write((first ? "" : ", ") + domain + *import + ".fbe." + listener);
            first = false;
        }
    }
    else
        Write(" : " + domain + "fbe.ISenderListener");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate sender listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateReceiver(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalReceiverListener" : "IReceiverListener");
    std::string receiver = (final ? "FinalReceiver" : "Receiver");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (receiver + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate receiver begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final receiver");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " receiver");
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"PrivatePropertyName\", \"UNUSED_PARAMETER\")");
    WriteLineIndent("open class " + receiver + " : " + domain + "fbe.Receiver, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported receivers accessors
    if (p->import)
    {
        WriteLineIndent("// Imported receivers");
        for (const auto& import : p->import->imports)
            WriteLineIndent("var " + *import + "Receiver: " + domain + *import + ".fbe." + receiver);
        WriteLine();
    }

    // Generate receiver models accessors
    if (p->body)
    {
        WriteLineIndent("// Receiver values accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("private val " + *s->name + "Value: " + struct_name);
            }
        }
        WriteLine();
        WriteLineIndent("// Receiver models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private val " + *s->name + "Model: " + *s->name + model);
        WriteLine();
    }

    // Generate receiver constructors
    WriteLineIndent("constructor() : super(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Receiver = " + domain + *import + ".fbe." + receiver + "(buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent(*s->name + "Value = " + struct_name + "()");
                WriteLineIndent(*s->name + "Model = " + *s->name + model + "()");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("constructor(buffer: " + domain + "fbe.Buffer) : super(buffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Receiver = " + domain + *import + ".fbe." + receiver + "(buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent(*s->name + "Value = " + struct_name + "()");
                WriteLineIndent(*s->name + "Model = " + *s->name + model + "()");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate receiver message handler
    WriteLineIndent("override fun onReceive(type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return onReceiveListener(this, type, buffer, offset, size)");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("open fun onReceiveListener(listener: " + listener + ", type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean");
    WriteLineIndent("{");
    Indent(1);
    if (p->body && messages)
    {
        WriteLineIndent("when (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent(domain + package + ".fbe." + *s->name + model + ".fbeTypeConst ->");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent(*s->name + "Model.attach(buffer, offset)");
                WriteLineIndent("assert(" + *s->name + "Model.verify()) { \"" + domain + *p->name + "." + *s->name + " validation failed!\" }");
                WriteLineIndent("val deserialized = " + *s->name + "Model.deserialize(" + *s->name + "Value)");
                WriteLineIndent("assert(deserialized > 0) { \"" + domain + *p->name + "." + *s->name + " deserialization failed!\" }");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("val message = " + *s->name + "Value.toString()");
                WriteLineIndent("onReceiveLog(message)");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("listener.onReceive(" + *s->name + "Value)");
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "Receiver.onReceiveListener(listener, type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate receiver end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateReceiverListener(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalReceiverListener" : "IReceiverListener");

    // Generate the file
    CppCommon::Path file = path / (listener + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate receiver listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final receiver listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " receiver listener interface");
    WriteIndent("interface " + listener);
    if (p->import)
    {
        bool first = true;
        Write(" : ");
        for (const auto& import : p->import->imports)
        {
            Write((first ? "" : ", ") + domain + *import + ".fbe." + listener);
            first = false;
        }
    }
    else
        Write(" : " + domain + "fbe.IReceiverListener");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate receiver listener handlers
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("fun onReceive(value: " + struct_name + ") {}");
            }
        }
    }

    // Generate receiver listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateProxy(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalProxyListener" : "IProxyListener");
    std::string proxy = (final ? "FinalProxy" : "Proxy");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (proxy + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate proxy begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final proxy");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " proxy");
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"PrivatePropertyName\", \"UNUSED_PARAMETER\")");
    WriteLineIndent("open class " + proxy + " : " + domain + "fbe.Receiver, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported proxy accessors
    if (p->import)
    {
        WriteLineIndent("// Imported proxy");
        for (const auto& import : p->import->imports)
            WriteLineIndent("var " + *import + "Proxy: " + domain + *import + ".fbe." + proxy);
        WriteLine();
    }

    // Generate proxy models accessors
    if (p->body)
    {
        WriteLineIndent("// Proxy models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private val " + *s->name + "Model: " + *s->name + model);
        WriteLine();
    }

    // Generate proxy constructors
    WriteLineIndent("constructor() : super(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Proxy = " + domain + *import + ".fbe." + proxy + "(buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = " + *s->name + model + "()");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("constructor(buffer: " + domain + "fbe.Buffer) : super(buffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Proxy = " + domain + *import + ".fbe." + proxy + "(buffer)");
    }
    if (p->body)
    {
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent(*s->name + "Model = " + *s->name + model + "()");
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate proxy message handler
    WriteLineIndent("override fun onReceive(type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return onReceiveListener(this, type, buffer, offset, size)");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("open fun onReceiveListener(listener: " + listener + ", type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean");
    WriteLineIndent("{");
    Indent(1);
    if (p->body && messages)
    {
        WriteLineIndent("when (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent(domain + package + ".fbe." + *s->name + model + ".fbeTypeConst ->");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Attach the FBE stream to the proxy model");
                WriteLineIndent(*s->name + "Model.attach(buffer, offset)");
                WriteLineIndent("assert(" + *s->name + "Model.verify()) { \"" + domain + *p->name + "." + *s->name + " validation failed!\" }");
                WriteLine();
                WriteLineIndent("val fbeBegin = " + *s->name + "Model.model.getBegin()");
                WriteLineIndent("if (fbeBegin == 0L)");
                Indent(1);
                WriteLineIndent("return false");
                Indent(-1);
                WriteLineIndent("// Call proxy handler");
                WriteLineIndent("listener.onProxy(" + *s->name + "Model, type, buffer, offset, size)");
                WriteLineIndent(*s->name + "Model.model.getEnd(fbeBegin)");
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "Proxy.onReceiveListener(listener, type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate proxy end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateProxyListener(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalProxyListener" : "IProxyListener");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (listener + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate proxy listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final proxy listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " proxy listener interface");
    WriteIndent("interface " + listener);
    if (p->import)
    {
        bool first = true;
        WriteIndent(" : ");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent((first ? "" : ", ") + domain + *import + ".fbe." + listener);
            first = false;
        }
    }
    else
        WriteIndent(" : " + domain + "fbe.IReceiverListener");
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate proxy listener handlers
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_model = *s->name + model;
                WriteLineIndent("fun onProxy(model: " + struct_model + ", type: Long, buffer: ByteArray, offset: Long, size: Long) {}");
            }
        }
    }

    // Generate proxy listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateClient(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalClientListener" : "IClientListener");
    std::string client = (final ? "FinalClient" : "Client");
    std::string model = (final ? "FinalModel" : "Model");

    // Generate the file
    CppCommon::Path file = path / (client + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate client begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final client");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " client");
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\", \"PropertyName\")");
    WriteLineIndent("open class " + client + " : " + domain + "fbe.Client, " + listener);
    WriteLineIndent("{");
    Indent(1);

    // Generate imported clients accessors
    if (p->import)
    {
        WriteLineIndent("// Imported clients");
        for (const auto& import : p->import->imports)
            WriteLineIndent("val " + *import + "Client: " + domain + *import + ".fbe." + client);
        WriteLine();
    }

    // Generate client sender models accessors
    if (p->body)
    {
        WriteLineIndent("// Client sender models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("val " + *s->name + "SenderModel: " + *s->name + model);
        WriteLine();
    }

    // Generate client receiver models accessors
    if (p->body)
    {
        WriteLineIndent("// Client receiver values accessors");
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("private val " + *s->name + "ReceiverValue: " + struct_name);
            }
        }
        WriteLine();
        WriteLineIndent("// Client receiver models accessors");
        for (const auto& s : p->body->structs)
            if (s->message)
                WriteLineIndent("private val " + *s->name + "ReceiverModel: " + *s->name + model);
        WriteLine();
    }

    // Generate client constructors
    WriteLineIndent("constructor() : super(" + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Client = " + domain + *import + ".fbe." + client + "(sendBuffer, receiveBuffer)");
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent(*s->name + "SenderModel = " + *s->name + model + "(sendBuffer)");
                WriteLineIndent(*s->name + "ReceiverValue = " + struct_name + "()");
                WriteLineIndent(*s->name + "ReceiverModel = " + *s->name + model + "()");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("constructor(sendBuffer: " + domain + "fbe.Buffer, receiveBuffer: " + domain + "fbe.Buffer) : super(sendBuffer, receiveBuffer, " + std::string(final ? "true" : "false") + ")");
    WriteLineIndent("{");
    Indent(1);
    if (p->import)
        for (const auto& import : p->import->imports)
            WriteLineIndent(*import + "Client = " + domain + *import + ".fbe." + client + "(sendBuffer, receiveBuffer)");
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent(*s->name + "SenderModel = " + *s->name + model + "(sendBuffer)");
                WriteLineIndent(*s->name + "ReceiverValue = " + struct_name + "()");
                WriteLineIndent(*s->name + "ReceiverModel = " + *s->name + model + "()");
            }
        }
    }
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    bool messages = false;
    for (const auto& s : p->body->structs)
        if (s->message)
            messages = true;

    // Generate generic client send method
    WriteLineIndent("fun send(obj: Any): Long");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return sendListener(this, obj)");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("@Suppress(\"JoinDeclarationAndAssignment\", \"UNUSED_PARAMETER\")");
    WriteLineIndent("fun sendListener(listener: " + listener + ", obj: Any): Long");
    WriteLineIndent("{");
    Indent(1);
    if (p->body && messages)
    {
        WriteLineIndent("when (obj)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("is " + struct_name + " -> if (obj.fbeType == " + *s->name + "SenderModel.fbeType) return sendListener(listener, obj)");
            }
        }
        Indent(-1);
        WriteLineIndent("}");
    }
    WriteLine();
    if (p->import)
    {
        WriteLineIndent("// Try to send using imported clients");
        WriteLineIndent("@Suppress(\"CanBeVal\")");
        WriteLineIndent("var result: Long");
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("result = " + *import + "Client.sendListener(listener, obj)");
            WriteLineIndent("if (result > 0)");
            Indent(1);
            WriteLineIndent("return result");
            Indent(-1);
        }
        WriteLine();
    }
    WriteLineIndent("return 0");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();

    // Generate client send methods
    if (p->body)
    {
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                std::string struct_name = domain + *p->name + "." + *s->name;
                WriteLineIndent("fun send(value: " + struct_name + "): Long");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("return sendListener(this, value)");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("fun sendListener(listener: " + listener + ", value: " + struct_name + "): Long");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Serialize the value into the FBE stream");
                WriteLineIndent("val serialized = " + *s->name + "SenderModel.serialize(value)");
                WriteLineIndent("assert(serialized > 0) { \"" + struct_name + " serialization failed!\" }");
                WriteLineIndent("assert(" + *s->name + "SenderModel.verify()) { \"" + struct_name + " validation failed!\" }");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("val message = value.toString()");
                WriteLineIndent("listener.onSendLog(message)");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Send the serialized value");
                WriteLineIndent("return sendSerialized(listener, serialized)");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
    }

    WriteLine();

    // Generate client receive message handler
    WriteLineIndent("override fun onReceive(type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent("return onReceiveListener(this, type, buffer, offset, size)");
    Indent(-1);
    WriteLineIndent("}");
    WriteLine();
    WriteLineIndent("open fun onReceiveListener(listener: " + listener + ", type: Long, buffer: ByteArray, offset: Long, size: Long): Boolean");
    WriteLineIndent("{");
    Indent(1);
    if (p->body && messages)
    {
        WriteLineIndent("when (type)");
        WriteLineIndent("{");
        Indent(1);
        for (const auto& s : p->body->structs)
        {
            if (s->message)
            {
                WriteLineIndent(domain + package + ".fbe." + *s->name + model + ".fbeTypeConst ->");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("// Deserialize the value from the FBE stream");
                WriteLineIndent(*s->name + "ReceiverModel.attach(buffer, offset)");
                WriteLineIndent("assert(" + *s->name + "ReceiverModel.verify()) { \"" + domain + *p->name + "." + *s->name + " validation failed!\" }");
                WriteLineIndent("val deserialized = " + *s->name + "ReceiverModel.deserialize(" + *s->name + "ReceiverValue)");
                WriteLineIndent("assert(deserialized > 0) { \"" + domain + *p->name + "." + *s->name + " deserialization failed!\" }");
                WriteLine();
                WriteLineIndent("// Log the value");
                WriteLineIndent("if (logging)");
                WriteLineIndent("{");
                Indent(1);
                WriteLineIndent("val message = " + *s->name + "ReceiverValue.toString()");
                WriteLineIndent("onReceiveLog(message)");
                Indent(-1);
                WriteLineIndent("}");
                WriteLine();
                WriteLineIndent("// Call receive handler with deserialized value");
                WriteLineIndent("listener.onReceive(" + *s->name + "ReceiverValue)");
                WriteLineIndent("return true");
                Indent(-1);
                WriteLineIndent("}");
            }
        }
        Indent(-1);
        WriteLineIndent("}");
    }
    if (p->import)
    {
        WriteLine();
        for (const auto& import : p->import->imports)
        {
            WriteLineIndent("if (" + *import + "Client.onReceiveListener(listener, type, buffer, offset, size))");
            Indent(1);
            WriteLineIndent("return true");
            Indent(-1);
        }
    }
    WriteLine();
    WriteLineIndent("return false");
    Indent(-1);
    WriteLineIndent("}");

    // Generate client end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateClientListener(const std::shared_ptr<Package>& p, bool final)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    std::string listener = (final ? "IFinalClientListener" : "IClientListener");
    std::string sender = (final ? "IFinalSenderListener" : "ISenderListener");
    std::string receiver = (final ? "IFinalReceiverListener" : "IReceiverListener");

    // Generate the file
    CppCommon::Path file = path / (listener + ".kt");
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate client listener begin
    WriteLine();
    if (final)
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " final client listener interface");
    else
        WriteLineIndent("// Fast Binary Encoding " + domain + *p->name + " client listener interface");
    WriteIndent("interface " + listener);
    if (p->import)
    {
        bool first = true;
        Write(" : ");
        for (const auto& import : p->import->imports)
        {
            Write((first ? "" : ", ") + domain + *import + ".fbe." + listener);
            first = false;
        }
    }
    else
        Write(" : " + domain + "fbe.IClientListener");
    Write(", " + sender + ", " + receiver);
    WriteLine();
    WriteLineIndent("{");
    Indent(1);

    // Generate client listener end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

void GeneratorKotlin::GenerateJson(const std::shared_ptr<Package>& p)
{
    std::string domain = (p->domain && !p->domain->empty()) ? (*p->domain + ".") : "";
    std::string package = *p->name;

    CppCommon::Path path = (CppCommon::Path(_output) / CreatePackagePath(domain, package)) / "fbe";

    // Create package path
    CppCommon::Directory::CreateTree(path);

    // Generate the file
    CppCommon::Path file = path / "Json.kt";
    WriteBegin();

    // Generate headers
    GenerateHeader(CppCommon::Path(_input).filename().string());
    GenerateImports(domain, package + ".fbe");

    // Generate JSON engine begin
    WriteLine();
    WriteLineIndent("// Fast Binary Encoding " + *p->name + " JSON engine");
    WriteLineIndent("object Json");
    WriteLineIndent("{");
    Indent(1);

    WriteLineIndent("// Get the JSON engine");
    WriteLineIndent("val engine: com.google.gson.Gson = register(com.google.gson.GsonBuilder()).create()");
    WriteLine();

    // Generate JSON engine Register() method
    WriteLineIndent("@Suppress(\"MemberVisibilityCanBePrivate\")");
    WriteLineIndent("fun register(builder: com.google.gson.GsonBuilder): com.google.gson.GsonBuilder");
    WriteLineIndent("{");
    Indent(1);
    WriteLineIndent(domain + "fbe.Json.register(builder)");
    if (p->import)
    {
        for (const auto& import : p->import->imports)
            WriteLineIndent(domain + *import + ".fbe.Json.register(builder)");
    }
    if (p->body)
    {
        for (const auto& e : p->body->enums)
            WriteLineIndent("builder.registerTypeAdapter(" + domain + package + "." + *e->name + "::class.java, " + *e->name + "Json())");
        for (const auto& f : p->body->flags)
            WriteLineIndent("builder.registerTypeAdapter(" + domain + package + "." + *f->name + "::class.java, " + *f->name + "Json())");
    }
    WriteLineIndent("return builder");
    Indent(-1);
    WriteLineIndent("}");

    // Generate JSON engine end
    Indent(-1);
    WriteLineIndent("}");

    // Generate footer
    GenerateFooter();

    // Store the file
    WriteEnd();
    Store(file);
}

bool GeneratorKotlin::IsKnownType(const std::string& type)
{
    return ((type == "bool") ||
            (type == "byte") || (type == "bytes") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double") ||
            (type == "decimal") || (type == "string") ||
            (type == "timestamp") || (type == "uuid"));
}

bool GeneratorKotlin::IsImportedType(const std::string& type)
{
    size_t pos = type.find_last_of('.');
    return (pos != std::string::npos);
}

bool GeneratorKotlin::IsPackageType(const std::string& type)
{
    if (IsKnownType(type) || IsImportedType(type))
        return true;

    return ((type == "Boolean") || (type == "Boolean?") ||
            (type == "Byte") || (type == "Byte?") || (type == "ByteArray") || (type == "ByteArray?") ||
            (type == "Char") || (type == "Char?") ||
            (type == "Byte") || (type == "Byte?") || (type == "UByte") || (type == "UByte?") ||
            (type == "Short") || (type == "Short?") || (type == "UShort") || (type == "UShort?") ||
            (type == "Int") || (type == "Int?") || (type == "UInt") || (type == "UInt?") ||
            (type == "Long") || (type == "Long?") || (type == "ULong") || (type == "ULong?") ||
            (type == "Float") || (type == "Float?") || (type == "Double") || (type == "Double?") ||
            (type == "java.math.BigDecimal") || (type == "java.math.BigDecimal?") || (type == "java.math.BigInteger") || (type == "java.math.BigInteger?") ||
            (type == "String") || (type == "String?") || (type == "java.util.Date") || (type == "java.util.Date?") || (type == "java.time.Instant") || (type == "java.time.Instant?") || (type == "java.util.UUID") || (type == "java.util.UUID?"));
}

bool GeneratorKotlin::IsPrimitiveType(const std::string& type, bool optional)
{
    return ((type == "bool") || (type == "byte") ||
            (type == "char") || (type == "wchar") ||
            (type == "int8") || (type == "uint8") ||
            (type == "int16") || (type == "uint16") ||
            (type == "int32") || (type == "uint32") ||
            (type == "int64") || (type == "uint64") ||
            (type == "float") || (type == "double") ||
            (type == "string"));
}

bool GeneratorKotlin::IsUnsignedType(const std::string& type)
{
    return ((type == "uint8") || (type == "uint16") || (type == "uint32") || (type == "uint64"));
}

std::string GeneratorKotlin::CreatePackagePath(const std::string& domain, const std::string& package)
{
    std::string result = domain;
    CppCommon::StringUtils::ReplaceAll(result, ".", std::string(1, CppCommon::Path::separator()));
    return result + CppCommon::Path::separator() + package;
}

std::string GeneratorKotlin::ConvertEnumBase(const std::string& type)
{
    if (type == "byte")
        return "Byte";
    else if (type == "char")
        return "Byte";
    else if (type == "wchar")
        return "Int";
    else if (type == "int8")
        return "Byte";
    else if (type == "uint8")
        return "UByte";
    else if (type == "int16")
        return "Short";
    else if (type == "uint16")
        return "UShort";
    else if (type == "int32")
        return "Int";
    else if (type == "uint32")
        return "UInt";
    else if (type == "int64")
        return "Long";
    else if (type == "uint64")
        return "ULong";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorKotlin::ConvertEnumType(const std::string& type)
{
    if (type == "byte")
        return "Byte";
    else if (type == "char")
        return "Byte";
    else if (type == "wchar")
        return "Int";
    else if (type == "int8")
        return "Byte";
    else if (type == "uint8")
        return "UByte";
    else if (type == "int16")
        return "Short";
    else if (type == "uint16")
        return "UShort";
    else if (type == "int32")
        return "Int";
    else if (type == "uint32")
        return "UInt";
    else if (type == "int64")
        return "Long";
    else if (type == "uint64")
        return "ULong";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorKotlin::ConvertEnumSize(const std::string& type)
{
    if (type == "byte")
        return "1";
    else if (type == "char")
        return "1";
    else if (type == "wchar")
        return "4";
    else if (type == "int8")
        return "1";
    else if (type == "uint8")
        return "1";
    else if (type == "int16")
        return "2";
    else if (type == "uint16")
        return "2";
    else if (type == "int32")
        return "4";
    else if (type == "uint32")
        return "4";
    else if (type == "int64")
        return "8";
    else if (type == "uint64")
        return "8";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorKotlin::ConvertEnumGet(const std::string& type)
{
    if (type == "byte")
        return "asByte";
    else if (type == "char")
        return "asByte";
    else if (type == "wchar")
        return "asInt";
    else if (type == "int8")
        return "asByte";
    else if (type == "uint8")
        return "asByte.toUByte()";
    else if (type == "int16")
        return "asShort";
    else if (type == "uint16")
        return "asShort.toUShort()";
    else if (type == "int32")
        return "asInt";
    else if (type == "uint32")
        return "asInt.toUInt()";
    else if (type == "int64")
        return "asLong";
    else if (type == "uint64")
        return "asLong.toULong()";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorKotlin::ConvertEnumRead(const std::string& type)
{
    if (type == "byte")
        return "readByte";
    else if (type == "char")
        return "readInt8";
    else if (type == "wchar")
        return "readInt32";
    else if (type == "int8")
        return "readInt8";
    else if (type == "uint8")
        return "readUInt8";
    else if (type == "int16")
        return "readInt16";
    else if (type == "uint16")
        return "readUInt16";
    else if (type == "int32")
        return "readInt32";
    else if (type == "uint32")
        return "readUInt32";
    else if (type == "int64")
        return "readInt64";
    else if (type == "uint64")
        return "readUInt64";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorKotlin::ConvertEnumFrom(const std::string& type)
{
    if (type == "uint8")
        return ".toByte()";
    else if (type == "uint16")
        return ".toShort()";
    else if (type == "uint32")
        return ".toInt()";
    else if (type == "uint64")
        return ".toLong()";

    return "";
}

std::string GeneratorKotlin::ConvertEnumTo(const std::string& type)
{
    if (type == "byte")
        return ".toByte()";
    else if (type == "char")
        return ".toByte()";
    else if (type == "wchar")
        return ".toInt()";
    else if (type == "int8")
        return ".toByte()";
    else if (type == "uint8")
        return ".toUByte()";
    else if (type == "int16")
        return ".toShort()";
    else if (type == "uint16")
        return ".toUShort()";
    else if (type == "int32")
        return ".toInt()";
    else if (type == "uint32")
        return ".toUInt()";
    else if (type == "int64")
        return ".toLong()";
    else if (type == "uint64")
        return ".toULong()";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorKotlin::ConvertEnumFlags(const std::string& type)
{
    if (type == "byte")
        return ".toInt()";
    else if (type == "int8")
        return ".toInt()";
    else if (type == "uint8")
        return ".toUInt()";
    else if (type == "int16")
        return ".toInt()";
    else if (type == "uint16")
        return ".toUInt()";
    else if (type == "int32")
        return ".toInt()";
    else if (type == "uint32")
        return ".toUInt()";
    else if (type == "int64")
        return ".toLong()";
    else if (type == "uint64")
        return ".toULong()";

    yyerror("Unsupported enum base type - " + type);
    return "";
}

std::string GeneratorKotlin::ConvertEnumConstant(const std::string& base, const std::string& type, const std::string& value, bool flag)
{
    std::string result = value;

    if (type.empty())
    {
        // Fill flags values
        std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);

        // Generate flags combination
        if (!flags.empty())
        {
            result = "";
            bool first = true;
            for (const auto& it : flags)
            {
                result += (first ? "" : " or ") + CppCommon::StringUtils::ToTrim(it) + ".raw" + (flag ? ConvertEnumFlags(base) : "");
                first = false;
            }
        }
    }

    return ConvertEnumConstantPrefix(type) + result + ConvertEnumConstantSuffix(type);
}

std::string GeneratorKotlin::ConvertEnumConstantPrefix(const std::string& type)
{
    return "";
}

std::string GeneratorKotlin::ConvertEnumConstantSuffix(const std::string& type)
{
    if ((type == "uint8") || (type == "uint16") || (type == "uint32"))
        return "u";
    if (type == "int64")
        return "L";
    if (type == "uint64")
        return "uL";

    return "";
}

std::string GeneratorKotlin::ConvertPrimitiveTypeName(const std::string& type)
{
    if (type == "bool")
        return "Boolean";
    else if (type == "byte")
        return "Byte";
    else if (type == "char")
        return "Character";
    else if (type == "wchar")
        return "Character";
    else if (type == "int8")
        return "Byte";
    else if (type == "uint8")
        return "UByte";
    else if (type == "int16")
        return "Short";
    else if (type == "uint16")
        return "UShort";
    else if (type == "int32")
        return "Int";
    else if (type == "uint32")
        return "UInt";
    else if (type == "int64")
        return "Long";
    else if (type == "uint64")
        return "ULong";
    else if (type == "float")
        return "Float";
    else if (type == "double")
        return "Double";

    return "";
}

std::string GeneratorKotlin::ConvertTypeName(const std::string& domain, const std::string& package, const std::string& type, bool optional)
{
    std::string opt = optional ? "?" : "";

    if (type == "bool")
        return "Boolean" + opt;
    else if (type == "byte")
        return "Byte" + opt;
    else if (type == "bytes")
        return "ByteArray" + opt;
    else if (type == "char")
        return "Char" + opt;
    else if (type == "wchar")
        return "Char" + opt;
    else if (type == "int8")
        return "Byte" + opt;
    else if (type == "uint8")
        return "UByte" + opt;
    else if (type == "int16")
        return "Short" + opt;
    else if (type == "uint16")
        return "UShort" + opt;
    else if (type == "int32")
        return "Int" + opt;
    else if (type == "uint32")
        return "UInt" + opt;
    else if (type == "int64")
        return "Long" + opt;
    else if (type == "uint64")
        return "ULong" + opt;
    else if (type == "float")
        return "Float" + opt;
    else if (type == "double")
        return "Double" + opt;
    else if (type == "decimal")
        return "java.math.BigDecimal" + opt;
    else if (type == "string")
        return "String" + opt;
    else if (type == "timestamp")
        return ((Version() < 8) ? "java.util.Date" : "java.time.Instant") + opt;
    else if (type == "uuid")
        return "java.util.UUID" + opt;

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else if (!package.empty())
        ns = domain + package + ".";

    return ns + t + opt;
}

std::string GeneratorKotlin::ConvertTypeName(const std::string& domain, const std::string& package, const StructField& field, bool typeless)
{
    if (field.array)
        return "Array" + (typeless ? "" : ("<" + ConvertTypeName(domain, package, *field.type, field.optional) + ">"));
    else if (field.vector)
        return "java.util.ArrayList" + (typeless ? "" : ("<" + ConvertTypeName(domain, package, *field.type, field.optional) + ">"));
    else if (field.list)
        return "java.util.LinkedList" + (typeless ? "" : ("<" + ConvertTypeName(domain, package, *field.type, field.optional) + ">"));
    else if (field.set)
        return "java.util.HashSet" + (typeless ? "" : ("<" + ConvertTypeName(domain, package, *field.key, field.optional) + ">"));
    else if (field.map)
        return "java.util.TreeMap" + (typeless ? "" : ("<" + ConvertTypeName(domain, package, *field.key, false) + ", " + ConvertTypeName(domain, package, *field.type, field.optional) +">"));
    else if (field.hash)
        return "java.util.HashMap" + (typeless ? "" : ("<" + ConvertTypeName(domain, package, *field.key, false) + ", " + ConvertTypeName(domain, package, *field.type, field.optional) +">"));

    return ConvertTypeName(domain, package, *field.type, field.optional);
}

std::string GeneratorKotlin::ConvertBaseFieldName(const std::string& domain, const std::string& type, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns.append("fbe.");
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ns + modelType + "Model" + ConvertTypeFieldName(t);
}

std::string GeneratorKotlin::ConvertTypeFieldName(const std::string& type)
{
    if (type == "bool")
        return "Boolean";
    else if (type == "byte")
        return "Byte";
    else if (type == "bytes")
        return "Bytes";
    else if (type == "char")
        return "Char";
    else if (type == "wchar")
        return "WChar";
    else if (type == "int8")
        return "Int8";
    else if (type == "uint8")
        return "UInt8";
    else if (type == "int16")
        return "Int16";
    else if (type == "uint16")
        return "UInt16";
    else if (type == "int32")
        return "Int32";
    else if (type == "uint32")
        return "UInt32";
    else if (type == "int64")
        return "Int64";
    else if (type == "uint64")
        return "UInt64";
    else if (type == "float")
        return "Float";
    else if (type == "double")
        return "Double";
    else if (type == "decimal")
        return "Decimal";
    else if (type == "string")
        return "String";
    else if (type == "timestamp")
        return ((Version() < 8) ? "Date" : "Timestamp");
    else if (type == "uuid")
        return "UUID";

    std::string result = type;
    CppCommon::StringUtils::ReplaceAll(result, ".", "");
    return result;
}

std::string GeneratorKotlin::ConvertTypeFieldType(const std::string& domain, const std::string& type, bool optional)
{
    std::string opt = optional ? "?" : "";

    if (type == "bool")
        return "Boolean" + opt;
    else if (type == "byte")
        return "Byte" + opt;
    else if (type == "bytes")
        return "ByteArray" + opt;
    else if (type == "char")
        return "Char" + opt;
    else if (type == "wchar")
        return "Char" + opt;
    else if (type == "int8")
        return "Byte" + opt;
    else if (type == "uint8")
        return "UByte" + opt;
    else if (type == "int16")
        return "Short" + opt;
    else if (type == "uint16")
        return "UShort" + opt;
    else if (type == "int32")
        return "Int" + opt;
    else if (type == "uint32")
        return "UInt" + opt;
    else if (type == "int64")
        return "Long" + opt;
    else if (type == "uint64")
        return "ULong" + opt;
    else if (type == "float")
        return "Float" + opt;
    else if (type == "double")
        return "Double" + opt;
    else if (type == "decimal")
        return "java.math.BigDecimal" + opt;
    else if (type == "string")
        return "String" + opt;
    else if (type == "timestamp")
        return ((Version() < 8) ? "java.util.Date" : "java.time.Instant") + opt;
    else if (type == "uuid")
        return "java.util.UUID" + opt;

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }

    return ns + t + opt;
}

std::string GeneratorKotlin::ConvertTypeFieldDeclaration(const std::string& domain, const std::string& type, bool optional, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    std::string ns = "";
    std::string opt = optional ? "Optional" : "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns.append("fbe.");
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else
        ns = (IsKnownType(type) && !optional) ? (domain + "fbe.") : "";

    return ns + modelType + "Model" + opt + ConvertTypeFieldName(t);
}

std::string GeneratorKotlin::ConvertTypeFieldDeclaration(const std::string& domain, const StructField& field, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return modelType + "ModelArray" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
    else if (field.vector || field.list || field.set)
        return modelType + "ModelVector" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
    else if (field.map || field.hash)
        return modelType + "ModelMap" + ConvertTypeFieldName(*field.key) + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type);
    else if (field.optional)
        return modelType + "ModelOptional" + ConvertTypeFieldName(*field.type);

    return ConvertTypeFieldDeclaration(domain, *field.type, field.optional, final);
}

std::string GeneratorKotlin::ConvertTypeFieldInitialization(const std::string& domain, const StructField& field, const std::string& offset, bool final)
{
    std::string modelType = final ? "Final" : "Field";

    if (field.array)
        return modelType + "ModelArray" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ", " + std::to_string(field.N) + ")";
    else if (field.vector || field.list || field.set)
        return modelType + "ModelVector" + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";
    else if (field.map || field.hash)
        return modelType + "ModelMap" + ConvertTypeFieldName(*field.key) + std::string(field.optional ? "Optional" : "") + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";
    else if (field.optional)
        return modelType + "ModelOptional" + ConvertTypeFieldName(*field.type) + "(buffer, " + offset + ")";

    std::string ns = "";
    std::string t = *field.type;
    std::string type = *field.type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns.append("fbe.");
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else
        ns = IsKnownType(type) ? (domain + "fbe.") : "";

    return ns + modelType + "Model" + ConvertTypeFieldName(t) + "(buffer, " + offset + ")";
}

std::string GeneratorKotlin::ConvertConstant(const std::string& domain, const std::string& package, const std::string& type, const std::string& value, bool optional)
{
    if (value == "true")
        return "true";
    else if (value == "false")
        return "false";
    else if (value == "null")
        return "null";
    else if (value == "min")
    {
        if (type == "byte")
            return ConvertConstantPrefix(type) + "0" + ConvertConstantSuffix(type);
        else if (type == "int8")
            return "Byte.MIN_VALUE";
        else if (type == "uint8")
            return "UByte.MIN_VALUE";
        else if (type == "int16")
            return "Short.MIN_VALUE";
        else if (type == "uint16")
            return "UShort.MIN_VALUE";
        else if (type == "int32")
            return "Int.MIN_VALUE";
        else if (type == "uint32")
            return "UInt.MIN_VALUE";
        else if (type == "int64")
            return "Long.MIN_VALUE";
        else if (type == "uint64")
            return "ULong.MIN_VALUE";

        yyerror("Unsupported type " + type + " for 'min' constant");
        return "";
    }
    else if (value == "max")
    {
        if (type == "byte")
            return ConvertConstantPrefix(type) + "0xFF" + ConvertConstantSuffix(type);
        else if (type == "int8")
            return "Byte.MAX_VALUE";
        else if (type == "uint8")
            return "UByte.MAX_VALUE";
        else if (type == "int16")
            return "Short.MAX_VALUE";
        else if (type == "uint16")
            return "UShort.MAX_VALUE";
        else if (type == "int32")
            return "Int.MAX_VALUE";
        else if (type == "uint32")
            return "UInt.MAX_VALUE";
        else if (type == "int64")
            return "Long.MAX_VALUE";
        else if (type == "uint64")
            return "ULong.MAX_VALUE";

        yyerror("Unsupported type " + type + " for 'max' constant");
        return "";
    }
    else if (value == "epoch")
        return ((Version() < 8) ? "java.util.Date(0)" : "java.time.Instant.EPOCH");
    else if (value == "utc")
        return ((Version() < 8) ? "java.util.Date(System.currentTimeMillis())" : "java.time.Instant.now()");
    else if (value == "uuid0")
        return domain + "fbe.UUIDGenerator.nil()";
    else if (value == "uuid1")
        return domain + "fbe.UUIDGenerator.sequential()";
    else if (value == "uuid4")
        return domain + "fbe.UUIDGenerator.random()";

    std::string result = value;

    if (!IsKnownType(type))
    {
        // Fill flags values
        std::vector<std::string> flags = CppCommon::StringUtils::Split(value, '|', true);

        // Generate flags combination
        if (flags.size() > 1)
        {
            result = "";
            bool first = true;
            for (const auto& it : flags)
            {
                std::string flag = CppCommon::StringUtils::ToTrim(it);
                size_t pos = flag.find_last_of('.');
                if (pos != std::string::npos)
                {
                    if (CppCommon::StringUtils::CountAll(flag, ".") > 1)
                        flag = domain + flag;
                    else
                        flag = (package.empty() ? "" : (domain + package + ".")) + flag;
                }
                result += (first ? "" : ", ") + flag + ".value";
                first = false;
            }
            result = (package.empty() ? "" : (domain + package + ".")) + type + ".fromSet(java.util.EnumSet.of(" + result + "))";
        }
        else
        {
            size_t pos = result.find_last_of('.');
            if (pos != std::string::npos)
            {
                if (CppCommon::StringUtils::CountAll(result, ".") > 1)
                    result = domain + result;
                else
                    result = (package.empty() ? "" : (domain + package + ".")) + result;
            }
        }
    }

    return ConvertConstantPrefix(type) + result + ConvertConstantSuffix(type);
}

std::string GeneratorKotlin::ConvertConstantPrefix(const std::string& type)
{
    if (type == "decimal")
        return "java.math.BigDecimal.valueOf(";
    if (type == "uuid")
        return "java.util.UUID.fromString(";

    return "";
}

std::string GeneratorKotlin::ConvertConstantSuffix(const std::string& type)
{
    if (type == "byte")
        return ".toByte()";
    if ((type == "char") || (type == "wchar"))
        return ".toChar()";
    if ((type == "uint8") || (type == "uint16") || (type == "uint32"))
        return "u";
    if (type == "int64")
        return "L";
    if (type == "uint64")
        return "uL";
    if (type == "float")
        return "f";

    if ((type == "decimal") || (type == "uuid"))
        return ")";

    return "";
}

std::string GeneratorKotlin::ConvertDefault(const std::string& domain, const std::string& package, const std::string& type)
{
    if (type == "bool")
        return "false";
    else if (type == "byte")
        return "0.toByte()";
    else if (type == "bytes")
        return "ByteArray(0)";
    else if ((type == "char") || (type == "wchar"))
        return "'\\u0000'";
    else if (type == "int8")
        return "0.toByte()";
    else if (type == "uint8")
        return "0.toUByte()";
    else if (type == "int16")
        return "0.toShort()";
    else if (type == "uint16")
        return "0.toUShort()";
    else if (type == "int32")
        return "0";
    else if (type == "uint32")
        return "0u";
    else if (type == "int64")
        return "0L";
    else if (type == "uint64")
        return "0uL";
    else if (type == "float")
        return "0.0f";
    else if (type == "double")
        return "0.0";
    else if (type == "decimal")
        return "java.math.BigDecimal.valueOf(0L)";
    else if (type == "string")
        return "\"\"";
    else if (type == "timestamp")
        return ((Version() < 8) ? "java.util.Date(0)" : "java.time.Instant.EPOCH");
    else if (type == "uuid")
        return domain + "fbe.UUIDGenerator.nil()";

    std::string ns = "";
    std::string t = type;

    size_t pos = type.find_last_of('.');
    if (pos != std::string::npos)
    {
        ns.assign(type, 0, pos + 1);
        ns = domain + ns;
        t.assign(type, pos + 1, type.size() - pos);
    }
    else if (!package.empty())
        ns = domain + package + ".";

    return ns + t + "()";
}

std::string GeneratorKotlin::ConvertDefault(const std::string& domain, const std::string& package, const StructField& field)
{
    if (field.value)
        return ConvertConstant(domain, package, *field.type, *field.value, field.optional);

    if (field.array)
        if (field.optional)
            return "arrayOfNulls<" + ConvertTypeName(domain, package, *field.type, field.optional) + ">(" + std::to_string(field.N) + ")";
        else
            return "Array(" + std::to_string(field.N) + ") { " + ConvertDefault(domain, package, *field.type) + " }";
    else if (field.vector || field.list || field.set || field.map || field.hash)
        return ConvertTypeName(domain, package, field, true) + "()";
    else if (field.optional)
        return "null";
    else if (!IsPackageType(*field.type))
        return ConvertTypeName(domain, package, field, true) + "()";

    return ConvertDefault(domain, package, *field.type);
}

std::string GeneratorKotlin::ConvertOutputStreamType(const std::string& type, const std::string& name, bool optional)
{
    std::string opt = optional ? "!!" : "";

    if (type == "bool")
        return ".append(if (" + name + opt + ") \"true\" else \"false\")";
    else if (type == "bytes")
        return ".append(\"bytes[\").append(" + name + opt + ".size).append(\"]\")";
    else if ((type == "char") || (type == "wchar"))
        return ".append(\"'\").append(" + name + opt + ").append(\"'\")";
    else if ((type == "string") || (type == "uuid"))
        return ".append(\"\\\"\").append(" + name + opt + ").append(\"\\\"\")";
    else if (type == "timestamp")
        return ((Version() < 8) ? ".append(" + name + opt + ".time * 1000000)" : ".append(" + name + opt + ".epochSecond * 1000000000 + " + name + opt + ".nano)");
    else
        return ".append(" + name + opt + ")";
}

std::string GeneratorKotlin::ConvertOutputStreamValue(const std::string& type, const std::string& name, bool optional, bool separate, bool nullable)
{
    std::string comma = separate ? ".append(if (first) \"\" else \",\")" : "";

    if (optional)
        return "if (" + name + " != null) sb" + comma + ConvertOutputStreamType(type, name, nullable) + "; else sb" + comma + ".append(\"null\")";
    else
        return "sb" + comma + ConvertOutputStreamType(type, name, false);
}

} // namespace FBE
